mirror of
https://github.com/FluxaMedia/fluxa.git
synced 2026-08-17 20:46:04 +00:00
Split TvSettingsSections.kt (818 lines) into focused section files
Dissolve the monolithic sections file into TvSettingsAccountSection, TvSettingsAppearanceSection, TvSettingsContentSection, TvSettingsPlaybackSection, and TvSettingsSystemSection. TvSettingsPreviews retains the preview composables. All section functions stay internal for same-module access from TvSettingsScreen.
This commit is contained in:
parent
331b989ce8
commit
423609a0e3
7 changed files with 1187 additions and 489 deletions
|
|
@ -0,0 +1,222 @@
|
|||
@file:OptIn(androidx.tv.material3.ExperimentalTvMaterial3Api::class, androidx.compose.material3.ExperimentalMaterial3Api::class)
|
||||
package com.fluxa.app.ui.catalog
|
||||
|
||||
import com.fluxa.app.common.AppStrings
|
||||
import com.fluxa.app.data.local.*
|
||||
import com.fluxa.app.data.remote.*
|
||||
import com.fluxa.app.data.repository.*
|
||||
import com.fluxa.app.domain.discovery.*
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.tv.material3.ClickableSurfaceDefaults
|
||||
import androidx.tv.material3.Surface
|
||||
import coil3.compose.AsyncImage
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
internal fun AccountSettings(
|
||||
profile: UserProfile,
|
||||
lang: String,
|
||||
onSwitchProfiles: () -> Unit,
|
||||
onConnectTrakt: () -> Unit,
|
||||
onConnectMal: () -> Unit,
|
||||
onConnectSimkl: () -> Unit,
|
||||
onUpdateProfile: (UserProfile) -> Unit
|
||||
) {
|
||||
var tmdbExpanded by remember { mutableStateOf(false) }
|
||||
SettingsSection(
|
||||
AppStrings.t(lang, "auto.account_sync"),
|
||||
AppStrings.t(lang, "auto.account_devices_and_sync")
|
||||
) {
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "settings.switch_profiles"),
|
||||
subtitle = AppStrings.t(lang, "settings.switch_profiles_desc"),
|
||||
icon = FluxaIcons.AccountCircle,
|
||||
onClick = onSwitchProfiles
|
||||
)
|
||||
}
|
||||
SettingsSection(
|
||||
AppStrings.t(lang, "settings.sync_with"),
|
||||
AppStrings.t(lang, "settings.sync_with_desc")
|
||||
) {
|
||||
SettingsConnectionTile(
|
||||
title = AppStrings.t(lang, "brand.trakt"),
|
||||
iconRes = com.fluxa.app.R.drawable.ic_trakt,
|
||||
value = if (!profile.traktAccessToken.isNullOrBlank()) {
|
||||
AppStrings.t(lang, "auto.connected")
|
||||
} else {
|
||||
AppStrings.t(lang, "auto.not_connected")
|
||||
},
|
||||
onClick = onConnectTrakt
|
||||
)
|
||||
SettingsConnectionTile(
|
||||
title = AppStrings.t(lang, "brand.myanimelist"),
|
||||
iconRes = com.fluxa.app.R.drawable.ic_myanimelist,
|
||||
value = if (!profile.malAccessToken.isNullOrBlank()) AppStrings.t(lang, "auto.connected") else AppStrings.t(lang, "auto.not_connected"),
|
||||
onClick = onConnectMal
|
||||
)
|
||||
SettingsConnectionTile(
|
||||
title = AppStrings.t(lang, "brand.simkl"),
|
||||
iconRes = com.fluxa.app.R.drawable.ic_simkl,
|
||||
value = if (!profile.simklAccessToken.isNullOrBlank()) AppStrings.t(lang, "auto.connected") else AppStrings.t(lang, "auto.not_connected"),
|
||||
onClick = onConnectSimkl
|
||||
)
|
||||
if (!profile.traktAccessToken.isNullOrBlank() || !profile.malAccessToken.isNullOrBlank() || !profile.simklAccessToken.isNullOrBlank()) {
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "auto.disconnect"),
|
||||
subtitle = AppStrings.t(lang, "integration.connected_accounts"),
|
||||
icon = FluxaIcons.Logout,
|
||||
accent = FluxaColors.errorRed,
|
||||
onClick = {
|
||||
onUpdateProfile(
|
||||
profile.copy(
|
||||
traktAccessToken = null,
|
||||
traktRefreshToken = null,
|
||||
traktLastSyncAt = null,
|
||||
traktLastSyncedItems = null,
|
||||
traktLastContinueWatchingCount = null,
|
||||
traktLastWatchlistCount = null,
|
||||
malAccessToken = null,
|
||||
malRefreshToken = null,
|
||||
simklAccessToken = null
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
SettingsSection(
|
||||
AppStrings.t(lang, "settings.apis"),
|
||||
AppStrings.t(lang, "settings.apis_desc")
|
||||
) {
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_api"),
|
||||
subtitle = if (profile.safeTmdbApiKey.isNotBlank()) AppStrings.t(lang, "settings.tmdb_api_configured") else AppStrings.t(lang, "settings.tmdb_api_not_configured"),
|
||||
icon = FluxaIcons.Storage,
|
||||
onClick = { tmdbExpanded = !tmdbExpanded }
|
||||
)
|
||||
if (tmdbExpanded) {
|
||||
SettingsTextFieldTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_api_key"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_api_key_desc"),
|
||||
value = profile.safeTmdbApiKey,
|
||||
placeholder = AppStrings.t(lang, "settings.tmdb_api_key_placeholder"),
|
||||
onValueChange = { onUpdateProfile(profile.copy(tmdbApiKey = it.trim())) }
|
||||
)
|
||||
if (profile.safeTmdbApiKey.isNotBlank()) {
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_cast_images"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_cast_images_desc"),
|
||||
checked = profile.safeTmdbCastImagesEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbCastImagesEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_similar_results"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_similar_results_desc"),
|
||||
checked = profile.safeTmdbSimilarResultsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbSimilarResultsEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_trailers"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_trailers_desc"),
|
||||
checked = profile.safeTmdbTrailersEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbTrailersEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_recommendations"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_recommendations_desc"),
|
||||
checked = profile.safeTmdbRecommendationsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbRecommendationsEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_collection_info"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_collection_info_desc"),
|
||||
checked = profile.safeTmdbCollectionInfoEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbCollectionInfoEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_episode_images"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_episode_images_desc"),
|
||||
checked = profile.safeTmdbEpisodeImagesEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbEpisodeImagesEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_logos_backdrops"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_logos_backdrops_desc"),
|
||||
checked = profile.safeTmdbLogosBackdropsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbLogosBackdropsEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_ratings"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_ratings_desc"),
|
||||
checked = profile.safeTmdbRatingsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbRatingsEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_basic_info"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_basic_info_desc"),
|
||||
checked = profile.safeTmdbBasicInfoEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbBasicInfoEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_details"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_details_desc"),
|
||||
checked = profile.safeTmdbDetailsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbDetailsEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_productions"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_productions_desc"),
|
||||
checked = profile.safeTmdbProductionsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbProductionsEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_networks"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_networks_desc"),
|
||||
checked = profile.safeTmdbNetworksEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbNetworksEnabled = it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
@file:OptIn(androidx.tv.material3.ExperimentalTvMaterial3Api::class, androidx.compose.material3.ExperimentalMaterial3Api::class)
|
||||
package com.fluxa.app.ui.catalog
|
||||
|
||||
import com.fluxa.app.common.AppStrings
|
||||
import com.fluxa.app.data.local.*
|
||||
import com.fluxa.app.data.remote.*
|
||||
import com.fluxa.app.data.repository.*
|
||||
import com.fluxa.app.domain.discovery.*
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.tv.material3.ClickableSurfaceDefaults
|
||||
import androidx.tv.material3.Surface
|
||||
import coil3.compose.AsyncImage
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
internal fun GeneralSettings(profile: UserProfile, lang: String, onUpdateProfile: (UserProfile) -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.general"), AppStrings.t(lang, "auto.app")) {
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.interface_language"),
|
||||
subtitle = AppStrings.t(lang, "auto.app_copy_and_category_labels"),
|
||||
options = listOf(
|
||||
ChoiceOption("none", AppStrings.t(lang, "settings.none")),
|
||||
ChoiceOption("tr", languageDisplayName("tr", lang)),
|
||||
ChoiceOption("en", languageDisplayName("en", lang))
|
||||
),
|
||||
selected = profile.safeLanguage,
|
||||
onSelect = { onUpdateProfile(profile.copy(language = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.start_page"),
|
||||
subtitle = AppStrings.t(lang, "settings.start_page_desc"),
|
||||
options = listOf(
|
||||
ChoiceOption("home", AppStrings.t(lang, "nav.home")),
|
||||
ChoiceOption("discover", AppStrings.t(lang, "nav.discover")),
|
||||
ChoiceOption("library", AppStrings.t(lang, "nav.library"))
|
||||
),
|
||||
selected = profile.safeStartPage,
|
||||
onSelect = { onUpdateProfile(profile.copy(startPage = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.tv_nav_layout"),
|
||||
subtitle = AppStrings.t(lang, "auto.tv_nav_layout_subtitle"),
|
||||
options = listOf(
|
||||
ChoiceOption("left", AppStrings.t(lang, "auto.tv_nav_layout_left")),
|
||||
ChoiceOption("top", AppStrings.t(lang, "auto.tv_nav_layout_top"))
|
||||
),
|
||||
selected = profile.safeTvNavLayout,
|
||||
onSelect = { onUpdateProfile(profile.copy(tvNavLayout = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "auto.background_playback"),
|
||||
subtitle = AppStrings.t(lang, "settings.background_playback_desc"),
|
||||
checked = profile.safeBackgroundPlayback,
|
||||
onToggle = { onUpdateProfile(profile.copy(backgroundPlayback = it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun AppearanceSettings(profile: UserProfile, lang: String, previewMeta: Meta?, onUpdateProfile: (UserProfile) -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.accent_color"), AppStrings.t(lang, "auto.appearance")) {
|
||||
SettingsAccentColorTile(profile, onUpdateProfile)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.amoled"),
|
||||
subtitle = AppStrings.t(lang, "auto.makes_the_background_and_bottom_bar_pure_bla"),
|
||||
checked = profile.safeAmoledMode,
|
||||
onToggle = { onUpdateProfile(profile.sanitizedUpdate(appTheme = "dark", amoledMode = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "auto.disable_animations"),
|
||||
subtitle = AppStrings.t(lang, "auto.turns_off_app_wide_transitions_and_card_anim"),
|
||||
checked = !profile.safeAnimationsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(animationsEnabled = !it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.card_layout"),
|
||||
subtitle = AppStrings.t(lang, "auto.how_rows_and_home_shelves_look"),
|
||||
options = listOf(
|
||||
ChoiceOption("vertical", AppStrings.t(lang, "auto.vertical_layout")),
|
||||
ChoiceOption("horizontal", AppStrings.t(lang, "auto.horizontal"))
|
||||
),
|
||||
selected = profile.safeCardLayout,
|
||||
onSelect = { onUpdateProfile(profile.copy(cardLayout = it)) }
|
||||
)
|
||||
}
|
||||
SettingsSection(AppStrings.t(lang, "settings.appearance_home_screen"), AppStrings.t(lang, "settings.appearance_home_screen_desc")) {
|
||||
TvPosterPreview(profile, lang, previewMeta)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.card_corners"),
|
||||
subtitle = "",
|
||||
options = mobilePosterCornerOptions(lang),
|
||||
selected = profile.safeCardCornerPreset,
|
||||
onSelect = { onUpdateProfile(profile.copy(cardCornerPreset = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.interface_density"),
|
||||
subtitle = "",
|
||||
options = mobilePresetOptions(lang),
|
||||
selected = profile.safeInterfaceDensity,
|
||||
onSelect = { onUpdateProfile(profile.copy(interfaceDensity = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.poster_width"),
|
||||
subtitle = "",
|
||||
options = mobilePosterWidthOptions(lang),
|
||||
selected = profile.safePosterWidthPreset,
|
||||
onSelect = { onUpdateProfile(profile.copy(posterWidthPreset = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.landscape_mode"),
|
||||
subtitle = "",
|
||||
checked = profile.safePosterLandscapeMode,
|
||||
onToggle = { onUpdateProfile(profile.copy(posterLandscapeMode = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "auto.hide_titles"),
|
||||
subtitle = "",
|
||||
checked = profile.safePosterHideTitles,
|
||||
onToggle = { onUpdateProfile(profile.copy(posterHideTitles = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.season_posters_on_hero"),
|
||||
subtitle = AppStrings.t(lang, "settings.home_season_posters_on_hero_desc"),
|
||||
checked = profile.safeHomeSeasonPostersOnHero,
|
||||
onToggle = { onUpdateProfile(profile.copy(homeSeasonPostersOnHero = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "auto.continue_watching"),
|
||||
subtitle = "",
|
||||
checked = profile.safeContinueWatchingEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(continueWatchingEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.continue_watching_hide_titles"),
|
||||
subtitle = "",
|
||||
checked = profile.safeContinueWatchingHideTitles,
|
||||
onToggle = { onUpdateProfile(profile.copy(continueWatchingHideTitles = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.continue_watching_layout"),
|
||||
subtitle = AppStrings.t(lang, "auto.show_that_shelf_as_posters_or_episode_cards"),
|
||||
options = listOf(
|
||||
ChoiceOption("vertical", AppStrings.t(lang, "auto.vertical_layout")),
|
||||
ChoiceOption("horizontal", AppStrings.t(lang, "auto.horizontal")),
|
||||
ChoiceOption("inherit", AppStrings.t(lang, "auto.match_global"))
|
||||
),
|
||||
selected = profile.safeContinueWatchingLayout,
|
||||
onSelect = { onUpdateProfile(profile.copy(continueWatchingLayout = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.series_artwork"),
|
||||
subtitle = AppStrings.t(lang, "auto.choose_episode_stills_or_regular_artwork_for"),
|
||||
options = listOf(
|
||||
ChoiceOption("episode", AppStrings.t(lang, "auto.episode_cover")),
|
||||
ChoiceOption("poster", AppStrings.t(lang, "auto.poster")),
|
||||
ChoiceOption("background", AppStrings.t(lang, "auto.backdrop"))
|
||||
),
|
||||
selected = profile.safeContinueWatchingArtwork,
|
||||
onSelect = { onUpdateProfile(profile.copy(continueWatchingArtwork = it)) }
|
||||
)
|
||||
}
|
||||
SettingsSection(AppStrings.t(lang, "settings.expanded_posters"), AppStrings.t(lang, "settings.expanded_posters_desc")) {
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.expanded_posters"),
|
||||
subtitle = AppStrings.t(lang, "settings.expanded_posters_desc"),
|
||||
checked = profile.safeExpandedPostersEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(expandedPostersEnabled = it)) }
|
||||
)
|
||||
if (profile.safeExpandedPostersEnabled) {
|
||||
SettingsSecondsSliderTile(
|
||||
title = AppStrings.t(lang, "settings.expanded_posters_delay"),
|
||||
subtitle = AppStrings.t(lang, "settings.expanded_posters_delay_desc"),
|
||||
value = profile.safeExpandedPostersDelaySeconds,
|
||||
valueRange = 0..10,
|
||||
zeroLabel = AppStrings.t(lang, "settings.expanded_posters_delay_instant"),
|
||||
onValueChange = { onUpdateProfile(profile.copy(expandedPostersDelaySeconds = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.trailer_on_expanded_posters"),
|
||||
subtitle = AppStrings.t(lang, "settings.trailer_on_expanded_posters_desc"),
|
||||
checked = profile.safeTrailerOnExpandedPostersEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(trailerOnExpandedPostersEnabled = it)) }
|
||||
)
|
||||
if (profile.safeTrailerOnExpandedPostersEnabled) {
|
||||
SettingsSecondsSliderTile(
|
||||
title = AppStrings.t(lang, "settings.trailer_on_expanded_posters_delay"),
|
||||
subtitle = AppStrings.t(lang, "settings.trailer_on_expanded_posters_delay_desc"),
|
||||
value = profile.safeTrailerOnExpandedPostersDelaySeconds,
|
||||
valueRange = 0..15,
|
||||
onValueChange = { onUpdateProfile(profile.copy(trailerOnExpandedPostersDelaySeconds = it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
SettingsSection(AppStrings.t(lang, "settings.appearance_detail_screen"), AppStrings.t(lang, "settings.appearance_detail_screen_desc")) {
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.trailer_on_hero"),
|
||||
subtitle = AppStrings.t(lang, "settings.trailer_on_hero_desc"),
|
||||
checked = profile.safeTrailerOnHero,
|
||||
onToggle = { onUpdateProfile(profile.copy(trailerOnHero = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.blur_unwatched_episodes"),
|
||||
subtitle = AppStrings.t(lang, "settings.blur_unwatched_episodes_desc"),
|
||||
checked = profile.safeBlurUnwatchedEpisodes,
|
||||
onToggle = { onUpdateProfile(profile.copy(blurUnwatchedEpisodes = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.season_selector"),
|
||||
subtitle = AppStrings.t(lang, "settings.season_selector_desc"),
|
||||
options = mobileSeasonSelectorOptions(lang),
|
||||
selected = profile.safeDetailSeasonSelectorMode,
|
||||
onSelect = { onUpdateProfile(profile.copy(detailSeasonSelectorMode = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.season_posters_on_hero"),
|
||||
subtitle = AppStrings.t(lang, "settings.detail_season_posters_on_hero_desc"),
|
||||
checked = profile.safeDetailSeasonPostersOnHero,
|
||||
onToggle = { onUpdateProfile(profile.copy(detailSeasonPostersOnHero = it)) }
|
||||
)
|
||||
TvEpisodeLayoutPreview(profile, lang) { onUpdateProfile(profile.copy(episodeCardsLayout = it)) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
@file:OptIn(androidx.tv.material3.ExperimentalTvMaterial3Api::class, androidx.compose.material3.ExperimentalMaterial3Api::class)
|
||||
package com.fluxa.app.ui.catalog
|
||||
|
||||
import com.fluxa.app.common.AppStrings
|
||||
import com.fluxa.app.data.local.*
|
||||
import com.fluxa.app.data.remote.*
|
||||
import com.fluxa.app.data.repository.*
|
||||
import com.fluxa.app.domain.discovery.*
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
internal fun ContentSettings(
|
||||
profile: UserProfile,
|
||||
lang: String,
|
||||
categories: List<HomeCategory>,
|
||||
userAddons: List<AddonDescriptor>,
|
||||
cs3FeedOptions: List<MetadataFeedOption>,
|
||||
onUpdateProfile: (UserProfile) -> Unit
|
||||
) {
|
||||
val metadataFeedOptions = buildMetadataFeedOptions(userAddons, lang) + cs3FeedOptions
|
||||
val heroFeedOptions = orderedMetadataFeeds(metadataFeedOptions, profile.heroFeedOrder)
|
||||
val homeFeedOptions = orderedMetadataFeeds(metadataFeedOptions, profile.homeFeedOrder)
|
||||
val metadataFeedKeys = metadataFeedOptions.map { it.key }
|
||||
val heroSelectedKeys = effectiveMetadataFeedSelection(profile.heroFeedToggles, metadataFeedKeys) ?: metadataFeedKeys.take(2)
|
||||
val homeSelectedKeys = effectiveMetadataFeedSelection(profile.homeFeedToggles, metadataFeedKeys)
|
||||
val homeVisibleFeedOptions = homeFeedOptions.filter { isMetadataFeedEnabled(homeSelectedKeys, it.key) }
|
||||
val topTenSelectedKeys = profile.safeTopTenFeedToggles
|
||||
|
||||
SettingsSection(AppStrings.t(lang, "settings.hero_catalogs"), "") {
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.show_hero_section"),
|
||||
subtitle = AppStrings.t(lang, "settings.show_hero_section_desc"),
|
||||
checked = profile.safeShowHeroSection,
|
||||
onToggle = { onUpdateProfile(profile.sanitizedUpdate(showHeroSection = it)) }
|
||||
)
|
||||
heroFeedOptions.forEachIndexed { index, option ->
|
||||
SettingsOrderedToggleTile(
|
||||
title = option.label,
|
||||
checked = heroSelectedKeys.contains(option.key),
|
||||
onToggle = {
|
||||
onUpdateProfile(profile.sanitizedUpdate(heroFeedToggles = toggleMetadataFeed(heroSelectedKeys, metadataFeedKeys, option.key, maxEnabled = 2)))
|
||||
},
|
||||
canMoveUp = index > 0,
|
||||
canMoveDown = index < heroFeedOptions.lastIndex,
|
||||
onMoveUp = { onUpdateProfile(profile.sanitizedUpdate(heroFeedOrder = moveMetadataFeedOrder(metadataFeedOptions, profile.heroFeedOrder, option.key, -1))) },
|
||||
onMoveDown = { onUpdateProfile(profile.sanitizedUpdate(heroFeedOrder = moveMetadataFeedOrder(metadataFeedOptions, profile.heroFeedOrder, option.key, 1))) }
|
||||
)
|
||||
}
|
||||
}
|
||||
SettingsSection(AppStrings.t(lang, "settings.home_catalogs"), AppStrings.t(lang, "settings.home_catalogs_desc")) {
|
||||
homeFeedOptions.forEachIndexed { index, option ->
|
||||
SettingsOrderedToggleTile(
|
||||
title = option.label,
|
||||
checked = isMetadataFeedEnabled(homeSelectedKeys, option.key),
|
||||
onToggle = {
|
||||
onUpdateProfile(profile.sanitizedUpdate(
|
||||
homeFeedToggles = toggleMetadataFeed(homeSelectedKeys, metadataFeedKeys, option.key),
|
||||
cs3FeedsConfigured = if (cs3FeedOptions.isNotEmpty()) true else profile.cs3FeedsConfigured
|
||||
))
|
||||
},
|
||||
canMoveUp = index > 0,
|
||||
canMoveDown = index < homeFeedOptions.lastIndex,
|
||||
onMoveUp = { onUpdateProfile(profile.sanitizedUpdate(homeFeedOrder = moveMetadataFeedOrder(metadataFeedOptions, profile.homeFeedOrder, option.key, -1))) },
|
||||
onMoveDown = { onUpdateProfile(profile.sanitizedUpdate(homeFeedOrder = moveMetadataFeedOrder(metadataFeedOptions, profile.homeFeedOrder, option.key, 1))) }
|
||||
)
|
||||
}
|
||||
}
|
||||
SettingsSection(AppStrings.t(lang, "settings.top_10_catalogs"), AppStrings.t(lang, "settings.top_10_catalogs_desc")) {
|
||||
homeVisibleFeedOptions.forEach { option ->
|
||||
SettingsToggleTile(
|
||||
title = option.label,
|
||||
subtitle = "",
|
||||
checked = topTenSelectedKeys.contains(option.key),
|
||||
onToggle = { onUpdateProfile(profile.sanitizedUpdate(topTenFeedToggles = toggleTopTenFeed(topTenSelectedKeys, option.key))) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun AdvancedSettings(profile: UserProfile, lang: String, onUpdateProfile: (UserProfile) -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "settings.advanced_settings"), AppStrings.t(lang, "settings.advanced_settings_desc")) {
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.buffer_cache"),
|
||||
subtitle = AppStrings.t(lang, "settings.buffer_cache_desc"),
|
||||
options = listOf(
|
||||
ChoiceOption("100", "100 MB"),
|
||||
ChoiceOption("500", "500 MB"),
|
||||
ChoiceOption("1000", "1 GB"),
|
||||
ChoiceOption("2000", "2 GB")
|
||||
),
|
||||
selected = profile.safePlayerBufferCacheMb.toString(),
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(playerBufferCacheMb = it.toInt())) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.forward_buffer"),
|
||||
subtitle = AppStrings.t(lang, "settings.forward_buffer_desc"),
|
||||
options = bufferSecondOptions(lang),
|
||||
selected = profile.safePlayerForwardBufferSeconds.toString(),
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(playerForwardBufferSeconds = it.toInt())) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.back_buffer"),
|
||||
subtitle = AppStrings.t(lang, "settings.back_buffer_desc"),
|
||||
options = bufferSecondOptions(lang, includeZero = true),
|
||||
selected = profile.safePlayerBackBufferSeconds.toString(),
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(playerBackBufferSeconds = it.toInt())) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.audio_decoder_mode"),
|
||||
subtitle = AppStrings.t(lang, "settings.audio_decoder_mode_desc"),
|
||||
options = audioDecoderModeOptions(lang),
|
||||
selected = profile.safeAudioDecoderMode,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(audioDecoderMode = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tunneled_playback"),
|
||||
subtitle = AppStrings.t(lang, "settings.tunneled_playback_desc"),
|
||||
checked = profile.safeTunneledPlayback,
|
||||
onToggle = { onUpdateProfile(profile.sanitizedUpdate(tunneledPlayback = it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
@file:OptIn(androidx.tv.material3.ExperimentalTvMaterial3Api::class, androidx.compose.material3.ExperimentalMaterial3Api::class)
|
||||
package com.fluxa.app.ui.catalog
|
||||
|
||||
import com.fluxa.app.common.AppStrings
|
||||
import com.fluxa.app.data.local.*
|
||||
import com.fluxa.app.data.remote.*
|
||||
import com.fluxa.app.data.repository.*
|
||||
import com.fluxa.app.domain.discovery.*
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
internal fun PlaybackSettings(
|
||||
profile: UserProfile,
|
||||
lang: String,
|
||||
onOpenSubtitles: () -> Unit,
|
||||
onOpenAdvanced: () -> Unit,
|
||||
onUpdateProfile: (UserProfile) -> Unit
|
||||
) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.playback"), AppStrings.t(lang, "auto.shape_how_playback_behaves")) {
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "auto.subtitles"),
|
||||
subtitle = AppStrings.t(lang, "settings.subtitles_settings_desc"),
|
||||
icon = FluxaIcons.Subtitles,
|
||||
onClick = onOpenSubtitles
|
||||
)
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "settings.advanced_settings"),
|
||||
subtitle = AppStrings.t(lang, "settings.advanced_settings_desc"),
|
||||
icon = FluxaIcons.Speed,
|
||||
onClick = onOpenAdvanced
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.skip_forward"),
|
||||
subtitle = AppStrings.t(lang, "auto.how_far_right_arrow_should_jump"),
|
||||
options = listOf(ChoiceOption("10", "10s"), ChoiceOption("15", "15s"), ChoiceOption("30", "30s")),
|
||||
selected = profile.safeSeekForwardSeconds.toString(),
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(seekForwardSeconds = it.toInt())) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.skip_back"),
|
||||
subtitle = AppStrings.t(lang, "auto.how_far_left_arrow_should_rewind"),
|
||||
options = listOf(ChoiceOption("10", "10s"), ChoiceOption("15", "15s"), ChoiceOption("30", "30s")),
|
||||
selected = profile.safeSeekBackwardSeconds.toString(),
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(seekBackwardSeconds = it.toInt())) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "auto.auto_skip_intros"),
|
||||
subtitle = AppStrings.t(lang, "auto.prefer_skipping_intros_where_supported"),
|
||||
checked = profile.safeAutoSkipIntro,
|
||||
onToggle = { onUpdateProfile(profile.sanitizedUpdate(autoSkipIntro = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.stream_source_selection"),
|
||||
subtitle = AppStrings.t(lang, "settings.stream_source_selection_desc"),
|
||||
options = streamSourceSelectionOptions(lang),
|
||||
selected = profile.safeStreamSourceSelectionMode,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(streamSourceSelectionMode = it)) }
|
||||
)
|
||||
if (profile.safeStreamSourceSelectionMode == STREAM_SOURCE_MODE_REGEX) {
|
||||
SettingsTextFieldTile(
|
||||
title = AppStrings.t(lang, "settings.regex_pattern"),
|
||||
subtitle = AppStrings.t(lang, "settings.regex_pattern_desc"),
|
||||
value = profile.safeStreamSourceRegexPattern,
|
||||
placeholder = AppStrings.t(lang, "settings.regex_pattern_placeholder"),
|
||||
onValueChange = { onUpdateProfile(profile.sanitizedUpdate(streamSourceRegexPattern = it)) }
|
||||
)
|
||||
}
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "auto.auto_play_next_episode"),
|
||||
subtitle = AppStrings.t(lang, "auto.queue_the_next_episode_automatically"),
|
||||
checked = profile.safeAutoPlayNextEpisode,
|
||||
onToggle = { onUpdateProfile(profile.sanitizedUpdate(autoPlayNextEpisode = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.try_binge_group"),
|
||||
subtitle = AppStrings.t(lang, "settings.try_binge_group_desc"),
|
||||
checked = profile.safeTryBingeGroup,
|
||||
onToggle = { onUpdateProfile(profile.sanitizedUpdate(tryBingeGroup = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.preferred_player"),
|
||||
subtitle = AppStrings.t(lang, "auto.choose_between_internal_or_external_playback"),
|
||||
options = listOf(
|
||||
ChoiceOption("exoplayer", "ExoPlayer"),
|
||||
ChoiceOption("mpv", "MPV")
|
||||
),
|
||||
selected = profile.safePreferredPlayer,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(preferredPlayer = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.dv_fallback"),
|
||||
subtitle = AppStrings.t(lang, "settings.dv_fallback_desc"),
|
||||
options = dolbyVisionFallbackOptions(lang),
|
||||
selected = profile.safeDolbyVisionFallbackMode,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(dolbyVisionFallbackMode = it)) }
|
||||
)
|
||||
if (profile.safeDolbyVisionFallbackMode == "auto") {
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.dv_rpu_mode"),
|
||||
subtitle = AppStrings.t(lang, "settings.dv_rpu_mode_desc"),
|
||||
options = dvRpuModeOptions(lang),
|
||||
selected = profile.safeDvRpuMode.toString(),
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(dvRpuMode = it.toIntOrNull() ?: 2)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.dv_zero_level5"),
|
||||
subtitle = AppStrings.t(lang, "settings.dv_zero_level5_desc"),
|
||||
checked = profile.safeDvZeroLevel5,
|
||||
onToggle = { onUpdateProfile(profile.sanitizedUpdate(dvZeroLevel5 = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.dv_hdr10plus_mode"),
|
||||
subtitle = AppStrings.t(lang, "settings.dv_hdr10plus_mode_desc"),
|
||||
options = dvHdr10PlusModeOptions(lang),
|
||||
selected = profile.safeDvHdr10PlusMode,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(dvHdr10PlusMode = it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
257
app/src/main/java/com/fluxa/app/ui/catalog/TvSettingsPreviews.kt
Normal file
257
app/src/main/java/com/fluxa/app/ui/catalog/TvSettingsPreviews.kt
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
@file:OptIn(androidx.tv.material3.ExperimentalTvMaterial3Api::class)
|
||||
package com.fluxa.app.ui.catalog
|
||||
|
||||
import com.fluxa.app.common.AppStrings
|
||||
import com.fluxa.app.data.local.*
|
||||
import com.fluxa.app.data.remote.*
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.tv.material3.Border
|
||||
import androidx.tv.material3.ClickableSurfaceDefaults
|
||||
import androidx.tv.material3.Surface
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
|
||||
@Composable
|
||||
internal fun TvPosterPreview(profile: UserProfile, lang: String, meta: Meta?) {
|
||||
val landscape = profile.safePosterLandscapeMode
|
||||
val width by animateDpAsState(
|
||||
targetValue = mobilePosterWidth(profile.safePosterWidthPreset, landscape),
|
||||
animationSpec = tween(FluxaDimensions.AnimDuration.contentExpand),
|
||||
label = "tvPosterPreviewWidth"
|
||||
)
|
||||
val height by animateDpAsState(
|
||||
targetValue = mobilePosterHeight(profile.safePosterWidthPreset, landscape),
|
||||
animationSpec = tween(FluxaDimensions.AnimDuration.contentExpand),
|
||||
label = "tvPosterPreviewHeight"
|
||||
)
|
||||
val radius by animateDpAsState(
|
||||
targetValue = mobileCornerRadius(profile.safeCardCornerPreset),
|
||||
animationSpec = tween(FluxaDimensions.AnimDuration.contentExpand),
|
||||
label = "tvPosterPreviewRadius"
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(width)
|
||||
.height(height)
|
||||
.clip(RoundedCornerShape(radius))
|
||||
.background(Color.White.copy(alpha = 0.06f))
|
||||
.border(1.dp, Color.White.copy(alpha = 0.38f), RoundedCornerShape(radius))
|
||||
) {
|
||||
val targetUrl = remember(meta?.id, meta?.poster, meta?.background, landscape) {
|
||||
if (landscape && meta != null) horizontalArtworkCandidates(meta).firstOrNull() else meta?.poster
|
||||
}
|
||||
var displayUrl by remember { mutableStateOf(targetUrl) }
|
||||
LaunchedEffect(targetUrl) {
|
||||
if (targetUrl != null) displayUrl = targetUrl
|
||||
}
|
||||
val context = LocalContext.current
|
||||
if (!displayUrl.isNullOrBlank()) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(context)
|
||||
.data(displayUrl)
|
||||
.crossfade(280)
|
||||
.build(),
|
||||
contentDescription = meta?.name,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Crop,
|
||||
onError = { displayUrl = null }
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
Brush.linearGradient(listOf(Color(0xFF2B3854), Color(0xFF7C3F54), Color(0xFF171B24)))
|
||||
)
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
listOf(Color.Transparent, Color.Black.copy(alpha = if (profile.safePosterHideTitles) 0.12f else 0.76f))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
if (!profile.safePosterHideTitles) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
text = meta?.name ?: AppStrings.t(lang, "auto.content_title"),
|
||||
color = Color.White.copy(alpha = 0.92f),
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Black,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.width(width)
|
||||
)
|
||||
Text(
|
||||
text = meta?.releaseInfo?.take(4) ?: meta?.released?.take(4) ?: "2026",
|
||||
color = Color.White.copy(alpha = 0.5f),
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.width(width)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun TvEpisodeLayoutPreview(profile: UserProfile, lang: String, onSelect: (String) -> Unit) {
|
||||
val isHorizontal = profile.safeEpisodeCardsLayout == "horizontal"
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
TvLayoutPreviewCard(
|
||||
title = AppStrings.t(lang, "settings.episode_layout_list"),
|
||||
selected = !isHorizontal,
|
||||
horizontal = false,
|
||||
onClick = { onSelect("list") }
|
||||
)
|
||||
Spacer(Modifier.width(20.dp))
|
||||
TvLayoutPreviewCard(
|
||||
title = AppStrings.t(lang, "settings.episode_layout_horizontal"),
|
||||
selected = isHorizontal,
|
||||
horizontal = true,
|
||||
onClick = { onSelect("horizontal") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TvLayoutPreviewCard(
|
||||
title: String,
|
||||
selected: Boolean,
|
||||
horizontal: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
var focused by remember { mutableStateOf(false) }
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Surface(
|
||||
onClick = onClick,
|
||||
modifier = Modifier
|
||||
.size(if (horizontal) 168.dp else 140.dp, if (horizontal) 106.dp else 118.dp)
|
||||
.onFocusChanged { focused = it.isFocused },
|
||||
shape = ClickableSurfaceDefaults.shape(RoundedCornerShape(14.dp)),
|
||||
colors = ClickableSurfaceDefaults.colors(
|
||||
containerColor = Color(0xFF171B24),
|
||||
contentColor = Color.White
|
||||
),
|
||||
border = ClickableSurfaceDefaults.border(
|
||||
border = Border(
|
||||
androidx.compose.foundation.BorderStroke(1.dp, if (selected) Color.White.copy(alpha = 0.75f) else Color.White.copy(alpha = 0.14f)),
|
||||
shape = RoundedCornerShape(14.dp)
|
||||
),
|
||||
focusedBorder = Border(
|
||||
androidx.compose.foundation.BorderStroke(2.dp, Color.White),
|
||||
shape = RoundedCornerShape(14.dp)
|
||||
)
|
||||
)
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize().padding(10.dp)) {
|
||||
if (horizontal) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(Color.White.copy(alpha = 0.14f))
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.88f)
|
||||
.height(4.dp)
|
||||
.clip(RoundedCornerShape(2.dp))
|
||||
.background(Color.White.copy(alpha = 0.55f))
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.65f)
|
||||
.height(3.dp)
|
||||
.clip(RoundedCornerShape(2.dp))
|
||||
.background(Color.White.copy(alpha = 0.22f))
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(48.dp)
|
||||
.fillMaxHeight()
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(Color.White.copy(alpha = 0.14f))
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp),
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.85f)
|
||||
.height(4.dp)
|
||||
.clip(RoundedCornerShape(2.dp))
|
||||
.background(Color.White.copy(alpha = 0.55f))
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.6f)
|
||||
.height(3.dp)
|
||||
.clip(RoundedCornerShape(2.dp))
|
||||
.background(Color.White.copy(alpha = 0.22f))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
title,
|
||||
color = if (selected || focused) Color.White else Color.White.copy(alpha = 0.6f),
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Black
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,489 +0,0 @@
|
|||
@file:OptIn(androidx.tv.material3.ExperimentalTvMaterial3Api::class, androidx.compose.material3.ExperimentalMaterial3Api::class)
|
||||
package com.fluxa.app.ui.catalog
|
||||
|
||||
import com.fluxa.app.common.AppStrings
|
||||
import com.fluxa.app.data.local.*
|
||||
import com.fluxa.app.data.remote.*
|
||||
import com.fluxa.app.data.repository.*
|
||||
import com.fluxa.app.domain.discovery.*
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.fluxa.app.R
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.tv.material3.ClickableSurfaceDefaults
|
||||
import androidx.tv.material3.Surface
|
||||
import coil3.compose.AsyncImage
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
internal fun AccountSettings(
|
||||
profile: UserProfile,
|
||||
lang: String,
|
||||
onSwitchProfiles: () -> Unit,
|
||||
onConnectTrakt: () -> Unit,
|
||||
onConnectMal: () -> Unit,
|
||||
onConnectSimkl: () -> Unit,
|
||||
onUpdateProfile: (UserProfile) -> Unit
|
||||
) {
|
||||
var tmdbExpanded by remember { mutableStateOf(false) }
|
||||
SettingsSection(
|
||||
AppStrings.t(lang, "auto.account_sync"),
|
||||
AppStrings.t(lang, "auto.account_devices_and_sync")
|
||||
) {
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "settings.switch_profiles"),
|
||||
subtitle = AppStrings.t(lang, "settings.switch_profiles_desc"),
|
||||
icon = FluxaIcons.AccountCircle,
|
||||
onClick = onSwitchProfiles
|
||||
)
|
||||
}
|
||||
SettingsSection(
|
||||
AppStrings.t(lang, "settings.sync_with"),
|
||||
AppStrings.t(lang, "settings.sync_with_desc")
|
||||
) {
|
||||
SettingsConnectionTile(
|
||||
title = AppStrings.t(lang, "brand.trakt"),
|
||||
iconRes = R.drawable.ic_trakt,
|
||||
value = if (!profile.traktAccessToken.isNullOrBlank()) {
|
||||
AppStrings.t(lang, "auto.connected")
|
||||
} else {
|
||||
AppStrings.t(lang, "auto.not_connected")
|
||||
},
|
||||
onClick = onConnectTrakt
|
||||
)
|
||||
SettingsConnectionTile(
|
||||
title = AppStrings.t(lang, "brand.myanimelist"),
|
||||
iconRes = R.drawable.ic_myanimelist,
|
||||
value = if (!profile.malAccessToken.isNullOrBlank()) AppStrings.t(lang, "auto.connected") else AppStrings.t(lang, "auto.not_connected"),
|
||||
onClick = onConnectMal
|
||||
)
|
||||
SettingsConnectionTile(
|
||||
title = AppStrings.t(lang, "brand.simkl"),
|
||||
iconRes = R.drawable.ic_simkl,
|
||||
value = if (!profile.simklAccessToken.isNullOrBlank()) AppStrings.t(lang, "auto.connected") else AppStrings.t(lang, "auto.not_connected"),
|
||||
onClick = onConnectSimkl
|
||||
)
|
||||
if (!profile.traktAccessToken.isNullOrBlank() || !profile.malAccessToken.isNullOrBlank() || !profile.simklAccessToken.isNullOrBlank()) {
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "auto.disconnect"),
|
||||
subtitle = AppStrings.t(lang, "integration.connected_accounts"),
|
||||
icon = FluxaIcons.Logout,
|
||||
accent = Color(0xFFFF6B6B),
|
||||
onClick = {
|
||||
onUpdateProfile(
|
||||
profile.copy(
|
||||
traktAccessToken = null,
|
||||
traktRefreshToken = null,
|
||||
traktLastSyncAt = null,
|
||||
traktLastSyncedItems = null,
|
||||
traktLastContinueWatchingCount = null,
|
||||
traktLastWatchlistCount = null,
|
||||
malAccessToken = null,
|
||||
malRefreshToken = null,
|
||||
simklAccessToken = null
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
SettingsSection(
|
||||
AppStrings.t(lang, "settings.apis"),
|
||||
AppStrings.t(lang, "settings.apis_desc")
|
||||
) {
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_api"),
|
||||
subtitle = if (profile.safeTmdbApiKey.isNotBlank()) AppStrings.t(lang, "settings.tmdb_api_configured") else AppStrings.t(lang, "settings.tmdb_api_not_configured"),
|
||||
icon = FluxaIcons.Storage,
|
||||
onClick = { tmdbExpanded = !tmdbExpanded }
|
||||
)
|
||||
if (tmdbExpanded) {
|
||||
SettingsTextFieldTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_api_key"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_api_key_desc"),
|
||||
value = profile.safeTmdbApiKey,
|
||||
placeholder = AppStrings.t(lang, "settings.tmdb_api_key_placeholder"),
|
||||
onValueChange = { onUpdateProfile(profile.copy(tmdbApiKey = it.trim())) }
|
||||
)
|
||||
if (profile.safeTmdbApiKey.isNotBlank()) {
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_cast_images"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_cast_images_desc"),
|
||||
checked = profile.safeTmdbCastImagesEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbCastImagesEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_similar_results"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_similar_results_desc"),
|
||||
checked = profile.safeTmdbSimilarResultsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbSimilarResultsEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_trailers"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_trailers_desc"),
|
||||
checked = profile.safeTmdbTrailersEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbTrailersEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_recommendations"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_recommendations_desc"),
|
||||
checked = profile.safeTmdbRecommendationsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbRecommendationsEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_collection_info"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_collection_info_desc"),
|
||||
checked = profile.safeTmdbCollectionInfoEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbCollectionInfoEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_episode_images"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_episode_images_desc"),
|
||||
checked = profile.safeTmdbEpisodeImagesEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbEpisodeImagesEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_logos_backdrops"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_logos_backdrops_desc"),
|
||||
checked = profile.safeTmdbLogosBackdropsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbLogosBackdropsEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_ratings"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_ratings_desc"),
|
||||
checked = profile.safeTmdbRatingsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbRatingsEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_basic_info"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_basic_info_desc"),
|
||||
checked = profile.safeTmdbBasicInfoEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbBasicInfoEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_details"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_details_desc"),
|
||||
checked = profile.safeTmdbDetailsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbDetailsEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_productions"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_productions_desc"),
|
||||
checked = profile.safeTmdbProductionsEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbProductionsEnabled = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.tmdb_networks"),
|
||||
subtitle = AppStrings.t(lang, "settings.tmdb_networks_desc"),
|
||||
checked = profile.safeTmdbNetworksEnabled,
|
||||
onToggle = { onUpdateProfile(profile.copy(tmdbNetworksEnabled = it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun InterfaceSettings(profile: UserProfile, lang: String, onUpdateProfile: (UserProfile) -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.interface_c0c2eda7"), AppStrings.t(lang, "auto.tune_language_and_visual_layout")) {
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.interface_language"),
|
||||
subtitle = AppStrings.t(lang, "auto.app_copy_and_category_labels"),
|
||||
options = listOf(
|
||||
ChoiceOption("none", AppStrings.t(lang, "settings.none")),
|
||||
ChoiceOption("tr", languageDisplayName("tr", lang)),
|
||||
ChoiceOption("en", languageDisplayName("en", lang))
|
||||
),
|
||||
selected = profile.safeLanguage,
|
||||
onSelect = { onUpdateProfile(profile.copy(language = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.card_layout"),
|
||||
subtitle = AppStrings.t(lang, "auto.how_rows_and_home_shelves_look"),
|
||||
options = listOf(
|
||||
ChoiceOption("vertical", AppStrings.t(lang, "auto.vertical_layout")),
|
||||
ChoiceOption("horizontal", AppStrings.t(lang, "auto.horizontal"))
|
||||
),
|
||||
selected = profile.safeCardLayout,
|
||||
onSelect = { onUpdateProfile(profile.copy(cardLayout = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.continue_watching_layout"),
|
||||
subtitle = AppStrings.t(lang, "auto.show_that_shelf_as_posters_or_episode_cards"),
|
||||
options = listOf(
|
||||
ChoiceOption("vertical", AppStrings.t(lang, "auto.vertical_layout")),
|
||||
ChoiceOption("horizontal", AppStrings.t(lang, "auto.horizontal")),
|
||||
ChoiceOption("inherit", AppStrings.t(lang, "auto.match_global"))
|
||||
),
|
||||
selected = profile.safeContinueWatchingLayout,
|
||||
onSelect = { onUpdateProfile(profile.copy(continueWatchingLayout = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.series_artwork"),
|
||||
subtitle = AppStrings.t(lang, "auto.choose_episode_stills_or_regular_artwork_for"),
|
||||
options = listOf(
|
||||
ChoiceOption("episode", AppStrings.t(lang, "auto.episode_cover")),
|
||||
ChoiceOption("poster", AppStrings.t(lang, "auto.poster")),
|
||||
ChoiceOption("background", AppStrings.t(lang, "auto.backdrop"))
|
||||
),
|
||||
selected = profile.safeContinueWatchingArtwork,
|
||||
onSelect = { onUpdateProfile(profile.copy(continueWatchingArtwork = it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun PlaybackSettings(profile: UserProfile, lang: String, onUpdateProfile: (UserProfile) -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.playback"), AppStrings.t(lang, "auto.shape_how_playback_behaves")) {
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.preferred_audio_language"),
|
||||
subtitle = AppStrings.t(lang, "auto.default_audio_track_preference"),
|
||||
options = audioLanguageOptions(lang),
|
||||
selected = profile.safePreferredAudioLanguage,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(preferredAudioLanguage = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.skip_forward"),
|
||||
subtitle = AppStrings.t(lang, "auto.how_far_right_arrow_should_jump"),
|
||||
options = listOf(ChoiceOption("10", "10s"), ChoiceOption("15", "15s"), ChoiceOption("30", "30s")),
|
||||
selected = profile.safeSeekForwardSeconds.toString(),
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(seekForwardSeconds = it.toInt())) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.skip_back"),
|
||||
subtitle = AppStrings.t(lang, "auto.how_far_left_arrow_should_rewind"),
|
||||
options = listOf(ChoiceOption("10", "10s"), ChoiceOption("15", "15s"), ChoiceOption("30", "30s")),
|
||||
selected = profile.safeSeekBackwardSeconds.toString(),
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(seekBackwardSeconds = it.toInt())) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "auto.auto_skip_intros"),
|
||||
subtitle = AppStrings.t(lang, "auto.prefer_skipping_intros_where_supported"),
|
||||
checked = profile.safeAutoSkipIntro,
|
||||
onToggle = { onUpdateProfile(profile.sanitizedUpdate(autoSkipIntro = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.stream_source_selection"),
|
||||
subtitle = AppStrings.t(lang, "settings.stream_source_selection_desc"),
|
||||
options = streamSourceSelectionOptions(lang),
|
||||
selected = profile.safeStreamSourceSelectionMode,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(streamSourceSelectionMode = it)) }
|
||||
)
|
||||
if (profile.safeStreamSourceSelectionMode == STREAM_SOURCE_MODE_REGEX) {
|
||||
SettingsTextFieldTile(
|
||||
title = AppStrings.t(lang, "settings.regex_pattern"),
|
||||
subtitle = AppStrings.t(lang, "settings.regex_pattern_desc"),
|
||||
value = profile.safeStreamSourceRegexPattern,
|
||||
placeholder = AppStrings.t(lang, "settings.regex_pattern_placeholder"),
|
||||
onValueChange = { onUpdateProfile(profile.sanitizedUpdate(streamSourceRegexPattern = it)) }
|
||||
)
|
||||
}
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "auto.auto_play_next_episode"),
|
||||
subtitle = AppStrings.t(lang, "auto.queue_the_next_episode_automatically"),
|
||||
checked = profile.safeAutoPlayNextEpisode,
|
||||
onToggle = { onUpdateProfile(profile.sanitizedUpdate(autoPlayNextEpisode = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.try_binge_group"),
|
||||
subtitle = AppStrings.t(lang, "settings.try_binge_group_desc"),
|
||||
checked = profile.safeTryBingeGroup,
|
||||
onToggle = { onUpdateProfile(profile.sanitizedUpdate(tryBingeGroup = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.preferred_player"),
|
||||
subtitle = AppStrings.t(lang, "auto.choose_between_internal_or_external_playback"),
|
||||
options = listOf(
|
||||
ChoiceOption("exoplayer", "ExoPlayer"),
|
||||
ChoiceOption("mpv", "MPV")
|
||||
),
|
||||
selected = profile.safePreferredPlayer,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(preferredPlayer = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.dv_fallback"),
|
||||
subtitle = AppStrings.t(lang, "settings.dv_fallback_desc"),
|
||||
options = dolbyVisionFallbackOptions(lang),
|
||||
selected = profile.safeDolbyVisionFallbackMode,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(dolbyVisionFallbackMode = it)) }
|
||||
)
|
||||
if (profile.safeDolbyVisionFallbackMode == "auto") {
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.dv_rpu_mode"),
|
||||
subtitle = AppStrings.t(lang, "settings.dv_rpu_mode_desc"),
|
||||
options = dvRpuModeOptions(lang),
|
||||
selected = profile.safeDvRpuMode.toString(),
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(dvRpuMode = it.toIntOrNull() ?: 2)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "settings.dv_zero_level5"),
|
||||
subtitle = AppStrings.t(lang, "settings.dv_zero_level5_desc"),
|
||||
checked = profile.safeDvZeroLevel5,
|
||||
onToggle = { onUpdateProfile(profile.sanitizedUpdate(dvZeroLevel5 = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.dv_hdr10plus_mode"),
|
||||
subtitle = AppStrings.t(lang, "settings.dv_hdr10plus_mode_desc"),
|
||||
options = dvHdr10PlusModeOptions(lang),
|
||||
selected = profile.safeDvHdr10PlusMode,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(dvHdr10PlusMode = it)) }
|
||||
)
|
||||
}
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.audio_decoder_mode"),
|
||||
subtitle = AppStrings.t(lang, "settings.audio_decoder_mode_desc"),
|
||||
options = audioDecoderModeOptions(lang),
|
||||
selected = profile.safeAudioDecoderMode,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(audioDecoderMode = it)) }
|
||||
)
|
||||
}
|
||||
SubtitleSettings(profile, lang, onUpdateProfile)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun DownloadSettings(profile: UserProfile, lang: String, onUpdateProfile: (UserProfile) -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.downloads"), AppStrings.t(lang, "settings.downloads_desc")) {
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.download_source_selection"),
|
||||
subtitle = AppStrings.t(lang, "settings.download_source_selection_desc"),
|
||||
options = streamSourceSelectionOptions(lang),
|
||||
selected = profile.safeDownloadSourceSelectionMode,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(downloadSourceSelectionMode = it)) }
|
||||
)
|
||||
if (profile.safeDownloadSourceSelectionMode == STREAM_SOURCE_MODE_REGEX) {
|
||||
SettingsTextFieldTile(
|
||||
title = AppStrings.t(lang, "settings.regex_pattern"),
|
||||
subtitle = AppStrings.t(lang, "settings.regex_pattern_desc"),
|
||||
value = profile.safeDownloadSourceRegexPattern,
|
||||
placeholder = AppStrings.t(lang, "settings.regex_pattern_placeholder"),
|
||||
onValueChange = { onUpdateProfile(profile.sanitizedUpdate(downloadSourceRegexPattern = it)) }
|
||||
)
|
||||
}
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.download_subtitle"),
|
||||
subtitle = AppStrings.t(lang, "settings.download_subtitle_desc"),
|
||||
options = downloadSubtitleOptions(lang),
|
||||
selected = profile.safeDownloadSubtitleLanguage,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(downloadSubtitleLanguage = it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SubtitleSettings(profile: UserProfile, lang: String, onUpdateProfile: (UserProfile) -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.subtitles_fc449c82"), AppStrings.t(lang, "auto.subtitle_language_size_and_readability")) {
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "auto.auto_enable_subtitles"),
|
||||
subtitle = AppStrings.t(lang, "auto.enable_subtitles_automatically_when_availabl"),
|
||||
checked = profile.safeAutoEnableSubtitles,
|
||||
onToggle = { onUpdateProfile(profile.copy(autoEnableSubtitles = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.preferred_subtitle_language"),
|
||||
subtitle = AppStrings.t(lang, "auto.default_subtitle_track_preference"),
|
||||
options = subtitleLanguageOptions(lang),
|
||||
selected = profile.safePreferredSubtitleLanguage,
|
||||
onSelect = { onUpdateProfile(profile.copy(preferredSubtitleLanguage = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.subtitle_size"),
|
||||
subtitle = AppStrings.t(lang, "auto.tune_readability_on_tv_and_mobile"),
|
||||
options = listOf(
|
||||
ChoiceOption("18", "18"),
|
||||
ChoiceOption("20", "20"),
|
||||
ChoiceOption("24", "24"),
|
||||
ChoiceOption("28", "28")
|
||||
),
|
||||
selected = profile.safeSubtitleSize.toInt().toString(),
|
||||
onSelect = { onUpdateProfile(profile.copy(subtitleSize = it.toFloat())) }
|
||||
)
|
||||
// Preview Background
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
.padding(24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
SubtitlePreviewText(
|
||||
text = AppStrings.t(lang, "settings.subtitle_preview"),
|
||||
profile = profile
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun AddonSettings(profile: UserProfile, lang: String, onManageAddons: () -> Unit, onUpdateProfile: (UserProfile) -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.add_ons_and_torrent"), AppStrings.t(lang, "auto.sources_and_stream_engine_preferences")) {
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "auto.manage_add_ons"),
|
||||
subtitle = AppStrings.t(lang, "auto.installed_add_ons_and_settings"),
|
||||
icon = FluxaIcons.Extension,
|
||||
onClick = onManageAddons
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SystemSettings(profile: UserProfile, lang: String, onReboot: () -> Unit, onLogout: () -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.system"), AppStrings.t(lang, "auto.app_and_device_actions")) {
|
||||
SettingsInfoTile(AppStrings.t(lang, "auto.version"), "1.2.5", FluxaIcons.Settings)
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "auto.restart_app"),
|
||||
subtitle = AppStrings.t(lang, "auto.refresh_the_app_after_playback_or_network_ch"),
|
||||
icon = FluxaIcons.Settings,
|
||||
onClick = onReboot
|
||||
)
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "auto.log_out"),
|
||||
subtitle = AppStrings.t(lang, "auto.sign_out_from_the_active_profile_on_this_dev"),
|
||||
icon = FluxaIcons.Logout,
|
||||
accent = Color(0xFFFF6B6B),
|
||||
onClick = onLogout
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
@file:OptIn(androidx.tv.material3.ExperimentalTvMaterial3Api::class, androidx.compose.material3.ExperimentalMaterial3Api::class)
|
||||
package com.fluxa.app.ui.catalog
|
||||
|
||||
import com.fluxa.app.common.AppStrings
|
||||
import com.fluxa.app.data.local.*
|
||||
import com.fluxa.app.data.remote.*
|
||||
import com.fluxa.app.data.repository.*
|
||||
import com.fluxa.app.domain.discovery.*
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
internal fun DownloadSettings(profile: UserProfile, lang: String, onUpdateProfile: (UserProfile) -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.downloads"), AppStrings.t(lang, "settings.downloads_desc")) {
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.download_source_selection"),
|
||||
subtitle = AppStrings.t(lang, "settings.download_source_selection_desc"),
|
||||
options = streamSourceSelectionOptions(lang),
|
||||
selected = profile.safeDownloadSourceSelectionMode,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(downloadSourceSelectionMode = it)) }
|
||||
)
|
||||
if (profile.safeDownloadSourceSelectionMode == STREAM_SOURCE_MODE_REGEX) {
|
||||
SettingsTextFieldTile(
|
||||
title = AppStrings.t(lang, "settings.regex_pattern"),
|
||||
subtitle = AppStrings.t(lang, "settings.regex_pattern_desc"),
|
||||
value = profile.safeDownloadSourceRegexPattern,
|
||||
placeholder = AppStrings.t(lang, "settings.regex_pattern_placeholder"),
|
||||
onValueChange = { onUpdateProfile(profile.sanitizedUpdate(downloadSourceRegexPattern = it)) }
|
||||
)
|
||||
}
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.download_subtitle"),
|
||||
subtitle = AppStrings.t(lang, "settings.download_subtitle_desc"),
|
||||
options = downloadSubtitleOptions(lang),
|
||||
selected = profile.safeDownloadSubtitleLanguage,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(downloadSubtitleLanguage = it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SubtitleSettings(profile: UserProfile, lang: String, onUpdateProfile: (UserProfile) -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.subtitles_fc449c82"), AppStrings.t(lang, "auto.subtitle_language_size_and_readability")) {
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.preferred_audio_language"),
|
||||
subtitle = AppStrings.t(lang, "settings.preferred_audio_language_desc"),
|
||||
options = audioLanguageOptions(lang),
|
||||
selected = profile.safePreferredAudioLanguage,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(preferredAudioLanguage = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.secondary_audio_language"),
|
||||
subtitle = AppStrings.t(lang, "settings.secondary_audio_language_desc"),
|
||||
options = audioLanguageOptions(lang),
|
||||
selected = profile.safeSecondaryAudioLanguage,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(secondaryAudioLanguage = it)) }
|
||||
)
|
||||
SettingsToggleTile(
|
||||
title = AppStrings.t(lang, "auto.auto_enable_subtitles"),
|
||||
subtitle = AppStrings.t(lang, "auto.enable_subtitles_automatically_when_availabl"),
|
||||
checked = profile.safeAutoEnableSubtitles,
|
||||
onToggle = { onUpdateProfile(profile.copy(autoEnableSubtitles = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.preferred_subtitle_language"),
|
||||
subtitle = AppStrings.t(lang, "auto.default_subtitle_track_preference"),
|
||||
options = subtitleLanguageOptions(lang),
|
||||
selected = profile.safePreferredSubtitleLanguage,
|
||||
onSelect = { onUpdateProfile(profile.copy(preferredSubtitleLanguage = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "settings.secondary_subtitle_language"),
|
||||
subtitle = AppStrings.t(lang, "settings.secondary_subtitle_language_desc"),
|
||||
options = subtitleLanguageOptions(lang),
|
||||
selected = profile.safeSecondarySubtitleLanguage,
|
||||
onSelect = { onUpdateProfile(profile.sanitizedUpdate(secondarySubtitleLanguage = it)) }
|
||||
)
|
||||
SettingsChoiceTile(
|
||||
title = AppStrings.t(lang, "auto.subtitle_size"),
|
||||
subtitle = AppStrings.t(lang, "auto.tune_readability_on_tv_and_mobile"),
|
||||
options = listOf(
|
||||
ChoiceOption("18", "18"),
|
||||
ChoiceOption("20", "20"),
|
||||
ChoiceOption("24", "24"),
|
||||
ChoiceOption("28", "28")
|
||||
),
|
||||
selected = profile.safeSubtitleSize.toInt().toString(),
|
||||
onSelect = { onUpdateProfile(profile.copy(subtitleSize = it.toFloat())) }
|
||||
)
|
||||
SettingsColorOpacityTile(
|
||||
title = AppStrings.t(lang, "settings.subtitle_text"),
|
||||
subtitle = AppStrings.t(lang, "settings.subtitle_text_desc"),
|
||||
opacityTitle = AppStrings.t(lang, "auto.text_transparency"),
|
||||
colorOptions = mobileSubtitleColorOptions(lang),
|
||||
selectedColor = profile.safeSubtitleColor,
|
||||
opacity = profile.safeSubtitleTextOpacity,
|
||||
onColorSelect = { onUpdateProfile(profile.copy(subtitleColor = it)) },
|
||||
onOpacity = { onUpdateProfile(profile.copy(subtitleTextOpacity = it)) }
|
||||
)
|
||||
SettingsColorOpacityTile(
|
||||
title = AppStrings.t(lang, "settings.subtitle_background"),
|
||||
subtitle = AppStrings.t(lang, "settings.subtitle_background_desc"),
|
||||
opacityTitle = AppStrings.t(lang, "auto.background_transparency"),
|
||||
colorOptions = mobileSubtitleColorOptions(lang),
|
||||
selectedColor = profile.safeSubtitleBackgroundColor,
|
||||
opacity = profile.safeSubtitleBackgroundOpacity,
|
||||
onColorSelect = { onUpdateProfile(profile.copy(subtitleBackgroundColor = it)) },
|
||||
onOpacity = { onUpdateProfile(profile.copy(subtitleBackgroundOpacity = it)) }
|
||||
)
|
||||
SettingsColorOpacityTile(
|
||||
title = AppStrings.t(lang, "settings.subtitle_outline"),
|
||||
subtitle = AppStrings.t(lang, "settings.subtitle_outline_desc"),
|
||||
opacityTitle = AppStrings.t(lang, "settings.subtitle.outline_opacity"),
|
||||
colorOptions = mobileSubtitleColorOptions(lang),
|
||||
selectedColor = profile.safeSubtitleOutlineColor,
|
||||
opacity = profile.safeSubtitleOutlineOpacity,
|
||||
onColorSelect = { onUpdateProfile(profile.copy(subtitleOutlineColor = it)) },
|
||||
onOpacity = { onUpdateProfile(profile.copy(subtitleOutlineOpacity = it)) }
|
||||
)
|
||||
androidx.compose.foundation.layout.Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
.padding(24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
SubtitlePreviewText(
|
||||
text = AppStrings.t(lang, "settings.subtitle_preview"),
|
||||
profile = profile
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun AddonSettings(profile: UserProfile, lang: String, onManageAddons: () -> Unit, onUpdateProfile: (UserProfile) -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.add_ons_and_torrent"), AppStrings.t(lang, "auto.sources_and_stream_engine_preferences")) {
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "auto.manage_add_ons"),
|
||||
subtitle = AppStrings.t(lang, "auto.installed_add_ons_and_settings"),
|
||||
icon = FluxaIcons.Extension,
|
||||
onClick = onManageAddons
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SystemSettings(profile: UserProfile, lang: String, onReboot: () -> Unit, onLogout: () -> Unit) {
|
||||
SettingsSection(AppStrings.t(lang, "auto.system"), AppStrings.t(lang, "auto.app_and_device_actions")) {
|
||||
SettingsInfoTile(AppStrings.t(lang, "auto.version"), "1.2.5", FluxaIcons.Settings)
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "auto.restart_app"),
|
||||
subtitle = AppStrings.t(lang, "auto.refresh_the_app_after_playback_or_network_ch"),
|
||||
icon = FluxaIcons.Settings,
|
||||
onClick = onReboot
|
||||
)
|
||||
SettingsActionTile(
|
||||
title = AppStrings.t(lang, "auto.log_out"),
|
||||
subtitle = AppStrings.t(lang, "auto.sign_out_from_the_active_profile_on_this_dev"),
|
||||
icon = FluxaIcons.Logout,
|
||||
accent = FluxaColors.errorRed,
|
||||
onClick = onLogout
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue