mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-28 07:22:22 +00:00
feat: adding search option on settings screen
This commit is contained in:
parent
88e1348c9b
commit
3d620394ca
4 changed files with 1199 additions and 35 deletions
|
|
@ -391,6 +391,9 @@
|
|||
<string name="compose_settings_root_switch_profile_description">Change to a different profile.</string>
|
||||
<string name="compose_settings_root_switch_profile_title">Switch Profile</string>
|
||||
<string name="compose_settings_root_trakt_description">Open Trakt connection screen</string>
|
||||
<string name="settings_search_empty">No settings found.</string>
|
||||
<string name="settings_search_placeholder">Search settings...</string>
|
||||
<string name="settings_search_results_section">RESULTS</string>
|
||||
<string name="compose_trakt_list_picker_loading">Loading your Trakt lists…</string>
|
||||
<string name="compose_trakt_list_picker_subtitle">Choose where to save this title on Trakt</string>
|
||||
<string name="action_donate">Donate</string>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -29,10 +30,19 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.max
|
||||
|
|
@ -66,8 +76,14 @@ import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepositor
|
|||
import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesUiState
|
||||
import nuvio.composeapp.generated.resources.Res
|
||||
import nuvio.composeapp.generated.resources.compose_settings_page_root
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
private val SettingsSearchRevealThreshold = 28.dp
|
||||
private const val SettingsSearchRevealAnimationMillis = 240L
|
||||
private const val SettingsSearchRevealHapticDelayMillis = 90L
|
||||
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -328,7 +344,66 @@ private fun MobileSettingsScreen(
|
|||
) {
|
||||
val saveableStateHolder = rememberSaveableStateHolder()
|
||||
saveableStateHolder.SaveableStateProvider(page.name) {
|
||||
NuvioScreen {
|
||||
var settingsSearchQuery by rememberSaveable { mutableStateOf("") }
|
||||
var rootSearchVisible by rememberSaveable { mutableStateOf(false) }
|
||||
var rootSearchRevealAnimating by rememberSaveable { mutableStateOf(false) }
|
||||
val listState = rememberLazyListState()
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val hapticScope = rememberCoroutineScope()
|
||||
val rootSearchRevealConnection = rememberSettingsRootSearchRevealConnection(
|
||||
page = page,
|
||||
listState = listState,
|
||||
query = settingsSearchQuery,
|
||||
searchVisible = rootSearchVisible,
|
||||
) {
|
||||
rootSearchVisible = true
|
||||
rootSearchRevealAnimating = true
|
||||
hapticScope.launch {
|
||||
delay(SettingsSearchRevealHapticDelayMillis)
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
||||
}
|
||||
}
|
||||
val searchEntries = settingsSearchEntries(
|
||||
pluginsEnabled = AppFeaturePolicy.pluginsEnabled,
|
||||
liquidGlassNativeTabBarSupported = liquidGlassNativeTabBarSupported,
|
||||
switchProfileAvailable = onSwitchProfile != null,
|
||||
checkForUpdatesAvailable = onCheckForUpdatesClick != null,
|
||||
)
|
||||
|
||||
fun openSearchTarget(target: SettingsSearchTarget) {
|
||||
when (target) {
|
||||
is SettingsSearchTarget.Page -> when (target.page) {
|
||||
SettingsPage.Account -> onAccountClick()
|
||||
SettingsPage.SupportersContributors -> onSupportersContributorsClick()
|
||||
SettingsPage.ContinueWatching -> onContinueWatchingClick()
|
||||
SettingsPage.Addons -> onAddonsClick()
|
||||
SettingsPage.Plugins -> {
|
||||
if (AppFeaturePolicy.pluginsEnabled) {
|
||||
onPluginsClick()
|
||||
}
|
||||
}
|
||||
SettingsPage.Homescreen -> onHomescreenClick()
|
||||
SettingsPage.MetaScreen -> onMetaScreenClick()
|
||||
else -> onPageChange(target.page)
|
||||
}
|
||||
SettingsSearchTarget.Downloads -> onDownloadsClick()
|
||||
SettingsSearchTarget.Collections -> onCollectionsClick()
|
||||
SettingsSearchTarget.SwitchProfile -> onSwitchProfile?.invoke()
|
||||
SettingsSearchTarget.CheckForUpdates -> onCheckForUpdatesClick?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(rootSearchRevealAnimating) {
|
||||
if (rootSearchRevealAnimating) {
|
||||
delay(SettingsSearchRevealAnimationMillis)
|
||||
rootSearchRevealAnimating = false
|
||||
}
|
||||
}
|
||||
|
||||
NuvioScreen(
|
||||
modifier = Modifier.nestedScroll(rootSearchRevealConnection),
|
||||
listState = listState,
|
||||
) {
|
||||
stickyHeader {
|
||||
val previousPage = page.previousPage()
|
||||
NuvioScreenHeader(
|
||||
|
|
@ -338,20 +413,33 @@ private fun MobileSettingsScreen(
|
|||
}
|
||||
|
||||
when (page) {
|
||||
SettingsPage.Root -> settingsRootContent(
|
||||
isTablet = false,
|
||||
onPlaybackClick = { onPageChange(SettingsPage.Playback) },
|
||||
onAppearanceClick = { onPageChange(SettingsPage.Appearance) },
|
||||
onNotificationsClick = { onPageChange(SettingsPage.Notifications) },
|
||||
onContentDiscoveryClick = { onPageChange(SettingsPage.ContentDiscovery) },
|
||||
onIntegrationsClick = { onPageChange(SettingsPage.Integrations) },
|
||||
onTraktClick = { onPageChange(SettingsPage.TraktAuthentication) },
|
||||
onSupportersContributorsClick = onSupportersContributorsClick,
|
||||
onCheckForUpdatesClick = onCheckForUpdatesClick,
|
||||
onDownloadsClick = onDownloadsClick,
|
||||
onAccountClick = onAccountClick,
|
||||
onSwitchProfileClick = onSwitchProfile,
|
||||
)
|
||||
SettingsPage.Root -> {
|
||||
settingsSearchRootContent(
|
||||
query = settingsSearchQuery,
|
||||
entries = searchEntries,
|
||||
isTablet = false,
|
||||
showSearchField = rootSearchVisible,
|
||||
animateSearchField = rootSearchRevealAnimating,
|
||||
onQueryChange = { settingsSearchQuery = it },
|
||||
onTargetClick = { openSearchTarget(it) },
|
||||
)
|
||||
if (settingsSearchQuery.isBlank()) {
|
||||
settingsRootContent(
|
||||
isTablet = false,
|
||||
onPlaybackClick = { onPageChange(SettingsPage.Playback) },
|
||||
onAppearanceClick = { onPageChange(SettingsPage.Appearance) },
|
||||
onNotificationsClick = { onPageChange(SettingsPage.Notifications) },
|
||||
onContentDiscoveryClick = { onPageChange(SettingsPage.ContentDiscovery) },
|
||||
onIntegrationsClick = { onPageChange(SettingsPage.Integrations) },
|
||||
onTraktClick = { onPageChange(SettingsPage.TraktAuthentication) },
|
||||
onSupportersContributorsClick = onSupportersContributorsClick,
|
||||
onCheckForUpdatesClick = onCheckForUpdatesClick,
|
||||
onDownloadsClick = onDownloadsClick,
|
||||
onAccountClick = onAccountClick,
|
||||
onSwitchProfileClick = onSwitchProfile,
|
||||
)
|
||||
}
|
||||
}
|
||||
SettingsPage.Account -> accountSettingsContent(
|
||||
isTablet = false,
|
||||
)
|
||||
|
|
@ -453,6 +541,48 @@ private fun MobileSettingsScreen(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberSettingsRootSearchRevealConnection(
|
||||
page: SettingsPage,
|
||||
listState: LazyListState,
|
||||
query: String,
|
||||
searchVisible: Boolean,
|
||||
onReveal: () -> Unit,
|
||||
): NestedScrollConnection {
|
||||
val revealThresholdPx = with(LocalDensity.current) { SettingsSearchRevealThreshold.toPx() }
|
||||
val currentOnReveal by rememberUpdatedState(onReveal)
|
||||
var pullDistancePx by remember(page) { mutableStateOf(0f) }
|
||||
var revealTriggered by remember(page) { mutableStateOf(false) }
|
||||
|
||||
return remember(page, listState, query, searchVisible, revealThresholdPx) {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPostScroll(
|
||||
consumed: Offset,
|
||||
available: Offset,
|
||||
source: NestedScrollSource,
|
||||
): Offset {
|
||||
val isRootAtTop = page == SettingsPage.Root &&
|
||||
listState.firstVisibleItemIndex == 0 &&
|
||||
listState.firstVisibleItemScrollOffset == 0
|
||||
val canRevealSearch = isRootAtTop && !searchVisible && !revealTriggered && query.isBlank()
|
||||
|
||||
if (canRevealSearch && available.y > 0f) {
|
||||
pullDistancePx += available.y
|
||||
if (pullDistancePx >= revealThresholdPx) {
|
||||
pullDistancePx = 0f
|
||||
revealTriggered = true
|
||||
currentOnReveal()
|
||||
}
|
||||
} else if (!isRootAtTop || available.y < 0f) {
|
||||
pullDistancePx = 0f
|
||||
}
|
||||
|
||||
return Offset.Zero
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TabletSettingsScreen(
|
||||
page: SettingsPage,
|
||||
|
|
@ -559,11 +689,54 @@ private fun TabletSettingsScreen(
|
|||
}
|
||||
|
||||
saveableStateHolder.SaveableStateProvider(page.name) {
|
||||
var settingsSearchQuery by rememberSaveable { mutableStateOf("") }
|
||||
var rootSearchVisible by rememberSaveable { mutableStateOf(false) }
|
||||
var rootSearchRevealAnimating by rememberSaveable { mutableStateOf(false) }
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val hapticScope = rememberCoroutineScope()
|
||||
val searchEntries = settingsSearchEntries(
|
||||
pluginsEnabled = AppFeaturePolicy.pluginsEnabled,
|
||||
liquidGlassNativeTabBarSupported = liquidGlassNativeTabBarSupported,
|
||||
switchProfileAvailable = onSwitchProfile != null,
|
||||
checkForUpdatesAvailable = onCheckForUpdatesClick != null,
|
||||
)
|
||||
|
||||
fun openSearchTarget(target: SettingsSearchTarget) {
|
||||
when (target) {
|
||||
is SettingsSearchTarget.Page -> openInlinePage(target.page)
|
||||
SettingsSearchTarget.Downloads -> onDownloadsClick()
|
||||
SettingsSearchTarget.Collections -> onCollectionsClick()
|
||||
SettingsSearchTarget.SwitchProfile -> onSwitchProfile?.invoke()
|
||||
SettingsSearchTarget.CheckForUpdates -> onCheckForUpdatesClick?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
val bottomOverlayPadding = LocalNuvioBottomNavigationOverlayPadding.current
|
||||
val rootSearchRevealConnection = rememberSettingsRootSearchRevealConnection(
|
||||
page = page,
|
||||
listState = listState,
|
||||
query = settingsSearchQuery,
|
||||
searchVisible = rootSearchVisible,
|
||||
) {
|
||||
rootSearchVisible = true
|
||||
rootSearchRevealAnimating = true
|
||||
hapticScope.launch {
|
||||
delay(SettingsSearchRevealHapticDelayMillis)
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(rootSearchRevealAnimating) {
|
||||
if (rootSearchRevealAnimating) {
|
||||
delay(SettingsSearchRevealAnimationMillis)
|
||||
rootSearchRevealAnimating = false
|
||||
}
|
||||
}
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(rootSearchRevealConnection),
|
||||
contentPadding = PaddingValues(
|
||||
start = 40.dp,
|
||||
top = topOffset,
|
||||
|
|
@ -576,7 +749,11 @@ private fun TabletSettingsScreen(
|
|||
val previousPage = page.previousPage()
|
||||
TabletPageHeader(
|
||||
title = if (page == SettingsPage.Root) {
|
||||
stringResource(activeCategory.labelRes)
|
||||
if (settingsSearchQuery.isBlank()) {
|
||||
stringResource(activeCategory.labelRes)
|
||||
} else {
|
||||
stringResource(Res.string.compose_settings_page_root)
|
||||
}
|
||||
} else {
|
||||
stringResource(page.titleRes)
|
||||
},
|
||||
|
|
@ -585,23 +762,36 @@ private fun TabletSettingsScreen(
|
|||
)
|
||||
}
|
||||
when (page) {
|
||||
SettingsPage.Root -> settingsRootContent(
|
||||
isTablet = true,
|
||||
onPlaybackClick = { openInlinePage(SettingsPage.Playback) },
|
||||
onAppearanceClick = { openInlinePage(SettingsPage.Appearance) },
|
||||
onNotificationsClick = { openInlinePage(SettingsPage.Notifications) },
|
||||
onContentDiscoveryClick = { openInlinePage(SettingsPage.ContentDiscovery) },
|
||||
onIntegrationsClick = { openInlinePage(SettingsPage.Integrations) },
|
||||
onTraktClick = { openInlinePage(SettingsPage.TraktAuthentication) },
|
||||
onSupportersContributorsClick = { openInlinePage(SettingsPage.SupportersContributors) },
|
||||
onCheckForUpdatesClick = onCheckForUpdatesClick,
|
||||
onDownloadsClick = onDownloadsClick,
|
||||
onAccountClick = { openInlinePage(SettingsPage.Account) },
|
||||
onSwitchProfileClick = onSwitchProfile,
|
||||
showAccountSection = activeCategory == SettingsCategory.Account,
|
||||
showGeneralSection = activeCategory == SettingsCategory.General,
|
||||
showAboutSection = activeCategory == SettingsCategory.About,
|
||||
)
|
||||
SettingsPage.Root -> {
|
||||
settingsSearchRootContent(
|
||||
query = settingsSearchQuery,
|
||||
entries = searchEntries,
|
||||
isTablet = true,
|
||||
showSearchField = rootSearchVisible,
|
||||
animateSearchField = rootSearchRevealAnimating,
|
||||
onQueryChange = { settingsSearchQuery = it },
|
||||
onTargetClick = { openSearchTarget(it) },
|
||||
)
|
||||
if (settingsSearchQuery.isBlank()) {
|
||||
settingsRootContent(
|
||||
isTablet = true,
|
||||
onPlaybackClick = { openInlinePage(SettingsPage.Playback) },
|
||||
onAppearanceClick = { openInlinePage(SettingsPage.Appearance) },
|
||||
onNotificationsClick = { openInlinePage(SettingsPage.Notifications) },
|
||||
onContentDiscoveryClick = { openInlinePage(SettingsPage.ContentDiscovery) },
|
||||
onIntegrationsClick = { openInlinePage(SettingsPage.Integrations) },
|
||||
onTraktClick = { openInlinePage(SettingsPage.TraktAuthentication) },
|
||||
onSupportersContributorsClick = { openInlinePage(SettingsPage.SupportersContributors) },
|
||||
onCheckForUpdatesClick = onCheckForUpdatesClick,
|
||||
onDownloadsClick = onDownloadsClick,
|
||||
onAccountClick = { openInlinePage(SettingsPage.Account) },
|
||||
onSwitchProfileClick = onSwitchProfile,
|
||||
showAccountSection = activeCategory == SettingsCategory.Account,
|
||||
showGeneralSection = activeCategory == SettingsCategory.General,
|
||||
showAboutSection = activeCategory == SettingsCategory.About,
|
||||
)
|
||||
}
|
||||
}
|
||||
SettingsPage.Account -> accountSettingsContent(
|
||||
isTablet = true,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,971 @@
|
|||
package com.nuvio.app.features.settings
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.AccountCircle
|
||||
import androidx.compose.material.icons.rounded.Close
|
||||
import androidx.compose.material.icons.rounded.CloudDownload
|
||||
import androidx.compose.material.icons.rounded.CollectionsBookmark
|
||||
import androidx.compose.material.icons.rounded.Extension
|
||||
import androidx.compose.material.icons.rounded.Favorite
|
||||
import androidx.compose.material.icons.rounded.Hub
|
||||
import androidx.compose.material.icons.rounded.Home
|
||||
import androidx.compose.material.icons.rounded.Language
|
||||
import androidx.compose.material.icons.rounded.Link
|
||||
import androidx.compose.material.icons.rounded.Notifications
|
||||
import androidx.compose.material.icons.rounded.Palette
|
||||
import androidx.compose.material.icons.rounded.People
|
||||
import androidx.compose.material.icons.rounded.PlayArrow
|
||||
import androidx.compose.material.icons.rounded.Search
|
||||
import androidx.compose.material.icons.rounded.Style
|
||||
import androidx.compose.material.icons.rounded.Tune
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.nuvio.app.isIos
|
||||
import nuvio.composeapp.generated.resources.*
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
internal sealed class SettingsSearchTarget {
|
||||
data class Page(val page: SettingsPage) : SettingsSearchTarget()
|
||||
object Downloads : SettingsSearchTarget()
|
||||
object Collections : SettingsSearchTarget()
|
||||
object SwitchProfile : SettingsSearchTarget()
|
||||
object CheckForUpdates : SettingsSearchTarget()
|
||||
}
|
||||
|
||||
internal data class SettingsSearchEntry(
|
||||
val key: String,
|
||||
val title: String,
|
||||
val description: String,
|
||||
val page: String,
|
||||
val section: String,
|
||||
val category: String,
|
||||
val icon: ImageVector,
|
||||
val target: SettingsSearchTarget,
|
||||
) {
|
||||
val searchableText: String = listOf(title, description, page, section, category)
|
||||
.joinToString(separator = " ")
|
||||
.lowercase()
|
||||
|
||||
val contextLabel: String = listOf(page, section)
|
||||
.filter { it.isNotBlank() }
|
||||
.distinct()
|
||||
.joinToString(separator = " - ")
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun settingsSearchEntries(
|
||||
pluginsEnabled: Boolean,
|
||||
liquidGlassNativeTabBarSupported: Boolean,
|
||||
switchProfileAvailable: Boolean,
|
||||
checkForUpdatesAvailable: Boolean,
|
||||
): List<SettingsSearchEntry> {
|
||||
val accountCategory = stringResource(SettingsCategory.Account.labelRes)
|
||||
val generalCategory = stringResource(SettingsCategory.General.labelRes)
|
||||
val aboutCategory = stringResource(SettingsCategory.About.labelRes)
|
||||
|
||||
val accountPage = stringResource(Res.string.compose_settings_page_account)
|
||||
val traktPage = stringResource(Res.string.compose_settings_page_trakt)
|
||||
val layoutPage = stringResource(Res.string.compose_settings_page_appearance)
|
||||
val contentDiscoveryPage = stringResource(Res.string.compose_settings_page_content_discovery)
|
||||
val downloadsPage = stringResource(Res.string.compose_settings_root_downloads_title)
|
||||
val playbackPage = stringResource(Res.string.compose_settings_page_playback)
|
||||
val integrationsPage = stringResource(Res.string.compose_settings_page_integrations)
|
||||
val notificationsPage = stringResource(Res.string.compose_settings_page_notifications)
|
||||
val supportersPage = stringResource(Res.string.compose_settings_page_supporters_contributors)
|
||||
val homeLayoutPage = stringResource(Res.string.compose_settings_page_homescreen)
|
||||
val detailPage = stringResource(Res.string.compose_settings_page_meta_screen)
|
||||
val continueWatchingPage = stringResource(Res.string.compose_settings_page_continue_watching)
|
||||
val posterStylePage = stringResource(Res.string.compose_settings_page_poster_customization)
|
||||
val addonsPage = stringResource(Res.string.compose_settings_page_addons)
|
||||
val pluginsPage = stringResource(Res.string.compose_settings_page_plugins)
|
||||
val collectionsPage = stringResource(Res.string.collections_header)
|
||||
val tmdbPage = stringResource(Res.string.compose_settings_page_tmdb_enrichment)
|
||||
val mdbListPage = stringResource(Res.string.compose_settings_page_mdblist_ratings)
|
||||
|
||||
val entries = mutableListOf<SettingsSearchEntry>()
|
||||
|
||||
fun add(
|
||||
key: String,
|
||||
title: String,
|
||||
description: String = "",
|
||||
page: String = title,
|
||||
section: String = "",
|
||||
category: String = generalCategory,
|
||||
icon: ImageVector,
|
||||
target: SettingsSearchTarget,
|
||||
) {
|
||||
entries += SettingsSearchEntry(
|
||||
key = key,
|
||||
title = title,
|
||||
description = description,
|
||||
page = page,
|
||||
section = section,
|
||||
category = category,
|
||||
icon = icon,
|
||||
target = target,
|
||||
)
|
||||
}
|
||||
|
||||
fun addPage(
|
||||
page: SettingsPage,
|
||||
key: String,
|
||||
title: String,
|
||||
description: String,
|
||||
category: String = generalCategory,
|
||||
icon: ImageVector,
|
||||
) {
|
||||
add(
|
||||
key = key,
|
||||
title = title,
|
||||
description = description,
|
||||
page = title,
|
||||
category = category,
|
||||
icon = icon,
|
||||
target = SettingsSearchTarget.Page(page),
|
||||
)
|
||||
}
|
||||
|
||||
fun addRow(
|
||||
page: SettingsPage,
|
||||
key: String,
|
||||
title: String,
|
||||
description: String = "",
|
||||
pageLabel: String,
|
||||
section: String,
|
||||
category: String = generalCategory,
|
||||
icon: ImageVector,
|
||||
) {
|
||||
add(
|
||||
key = key,
|
||||
title = title,
|
||||
description = description,
|
||||
page = pageLabel,
|
||||
section = section,
|
||||
category = category,
|
||||
icon = icon,
|
||||
target = SettingsSearchTarget.Page(page),
|
||||
)
|
||||
}
|
||||
|
||||
if (switchProfileAvailable) {
|
||||
add(
|
||||
key = "switch-profile",
|
||||
title = stringResource(Res.string.compose_settings_root_switch_profile_title),
|
||||
description = stringResource(Res.string.compose_settings_root_switch_profile_description),
|
||||
page = accountPage,
|
||||
section = stringResource(Res.string.compose_settings_root_account_section),
|
||||
category = accountCategory,
|
||||
icon = Icons.Rounded.People,
|
||||
target = SettingsSearchTarget.SwitchProfile,
|
||||
)
|
||||
}
|
||||
addPage(
|
||||
page = SettingsPage.Account,
|
||||
key = "account",
|
||||
title = accountPage,
|
||||
description = stringResource(Res.string.compose_settings_root_account_description),
|
||||
category = accountCategory,
|
||||
icon = Icons.Rounded.AccountCircle,
|
||||
)
|
||||
addPage(
|
||||
page = SettingsPage.TraktAuthentication,
|
||||
key = "trakt",
|
||||
title = traktPage,
|
||||
description = stringResource(Res.string.compose_settings_root_trakt_description),
|
||||
category = accountCategory,
|
||||
icon = Icons.Rounded.Link,
|
||||
)
|
||||
addPage(
|
||||
page = SettingsPage.Appearance,
|
||||
key = "layout",
|
||||
title = layoutPage,
|
||||
description = stringResource(Res.string.compose_settings_root_appearance_description),
|
||||
icon = Icons.Rounded.Palette,
|
||||
)
|
||||
addPage(
|
||||
page = SettingsPage.ContentDiscovery,
|
||||
key = "content-discovery",
|
||||
title = contentDiscoveryPage,
|
||||
description = stringResource(Res.string.compose_settings_root_content_discovery_description),
|
||||
icon = Icons.Rounded.Extension,
|
||||
)
|
||||
add(
|
||||
key = "downloads",
|
||||
title = downloadsPage,
|
||||
description = stringResource(Res.string.compose_settings_root_downloads_description),
|
||||
category = generalCategory,
|
||||
icon = Icons.Rounded.CloudDownload,
|
||||
target = SettingsSearchTarget.Downloads,
|
||||
)
|
||||
addPage(
|
||||
page = SettingsPage.Playback,
|
||||
key = "playback",
|
||||
title = playbackPage,
|
||||
description = stringResource(Res.string.settings_playback_subtitle),
|
||||
icon = Icons.Rounded.PlayArrow,
|
||||
)
|
||||
addPage(
|
||||
page = SettingsPage.Integrations,
|
||||
key = "integrations",
|
||||
title = integrationsPage,
|
||||
description = stringResource(Res.string.compose_settings_root_integrations_description),
|
||||
icon = Icons.Rounded.Link,
|
||||
)
|
||||
addPage(
|
||||
page = SettingsPage.Notifications,
|
||||
key = "notifications",
|
||||
title = notificationsPage,
|
||||
description = stringResource(Res.string.compose_settings_root_notifications_description),
|
||||
icon = Icons.Rounded.Notifications,
|
||||
)
|
||||
addPage(
|
||||
page = SettingsPage.SupportersContributors,
|
||||
key = "supporters",
|
||||
title = supportersPage,
|
||||
description = stringResource(Res.string.about_supporters_contributors_subtitle),
|
||||
category = aboutCategory,
|
||||
icon = Icons.Rounded.Favorite,
|
||||
)
|
||||
if (checkForUpdatesAvailable) {
|
||||
add(
|
||||
key = "check-updates",
|
||||
title = stringResource(Res.string.compose_settings_root_check_updates_title),
|
||||
description = stringResource(Res.string.compose_settings_root_check_updates_description),
|
||||
page = supportersPage,
|
||||
section = stringResource(Res.string.compose_settings_root_about_section),
|
||||
category = aboutCategory,
|
||||
icon = Icons.Rounded.CloudDownload,
|
||||
target = SettingsSearchTarget.CheckForUpdates,
|
||||
)
|
||||
}
|
||||
|
||||
addRow(
|
||||
page = SettingsPage.Account,
|
||||
key = "account-status",
|
||||
title = stringResource(Res.string.settings_account_status),
|
||||
pageLabel = accountPage,
|
||||
section = accountPage,
|
||||
category = accountCategory,
|
||||
icon = Icons.Rounded.AccountCircle,
|
||||
)
|
||||
addRow(
|
||||
page = SettingsPage.Account,
|
||||
key = "account-sign-out",
|
||||
title = stringResource(Res.string.settings_account_sign_out),
|
||||
pageLabel = accountPage,
|
||||
section = accountPage,
|
||||
category = accountCategory,
|
||||
icon = Icons.Rounded.AccountCircle,
|
||||
)
|
||||
addRow(
|
||||
page = SettingsPage.Account,
|
||||
key = "account-delete",
|
||||
title = stringResource(Res.string.settings_account_delete_account),
|
||||
description = stringResource(Res.string.settings_account_delete_account_description),
|
||||
pageLabel = accountPage,
|
||||
section = accountPage,
|
||||
category = accountCategory,
|
||||
icon = Icons.Rounded.AccountCircle,
|
||||
)
|
||||
|
||||
addRow(
|
||||
page = SettingsPage.Appearance,
|
||||
key = "theme",
|
||||
title = stringResource(Res.string.settings_appearance_section_theme),
|
||||
pageLabel = layoutPage,
|
||||
section = stringResource(Res.string.settings_appearance_section_theme),
|
||||
icon = Icons.Rounded.Palette,
|
||||
)
|
||||
addRow(
|
||||
page = SettingsPage.Appearance,
|
||||
key = "amoled",
|
||||
title = stringResource(Res.string.settings_appearance_amoled_black),
|
||||
description = stringResource(Res.string.settings_appearance_amoled_description),
|
||||
pageLabel = layoutPage,
|
||||
section = stringResource(Res.string.settings_appearance_section_display),
|
||||
icon = Icons.Rounded.Palette,
|
||||
)
|
||||
if (liquidGlassNativeTabBarSupported) {
|
||||
addRow(
|
||||
page = SettingsPage.Appearance,
|
||||
key = "liquid-glass",
|
||||
title = stringResource(Res.string.settings_appearance_liquid_glass),
|
||||
description = stringResource(Res.string.settings_appearance_liquid_glass_description),
|
||||
pageLabel = layoutPage,
|
||||
section = stringResource(Res.string.settings_appearance_section_display),
|
||||
icon = Icons.Rounded.Palette,
|
||||
)
|
||||
}
|
||||
addRow(
|
||||
page = SettingsPage.Appearance,
|
||||
key = "app-language",
|
||||
title = stringResource(Res.string.settings_appearance_app_language),
|
||||
pageLabel = layoutPage,
|
||||
section = stringResource(Res.string.settings_appearance_section_display),
|
||||
icon = Icons.Rounded.Language,
|
||||
)
|
||||
addPage(
|
||||
page = SettingsPage.ContinueWatching,
|
||||
key = "continue-watching",
|
||||
title = continueWatchingPage,
|
||||
description = stringResource(Res.string.settings_appearance_continue_watching_description),
|
||||
icon = Icons.Rounded.Style,
|
||||
)
|
||||
addPage(
|
||||
page = SettingsPage.PosterCustomization,
|
||||
key = "poster-card-style",
|
||||
title = posterStylePage,
|
||||
description = stringResource(Res.string.settings_appearance_poster_customization_description),
|
||||
icon = Icons.Rounded.Tune,
|
||||
)
|
||||
|
||||
addPage(
|
||||
page = SettingsPage.Addons,
|
||||
key = "addons",
|
||||
title = addonsPage,
|
||||
description = stringResource(Res.string.settings_content_discovery_addons_description),
|
||||
icon = Icons.Rounded.Extension,
|
||||
)
|
||||
if (pluginsEnabled) {
|
||||
addPage(
|
||||
page = SettingsPage.Plugins,
|
||||
key = "plugins",
|
||||
title = pluginsPage,
|
||||
description = stringResource(Res.string.settings_content_discovery_plugins_description),
|
||||
icon = Icons.Rounded.Hub,
|
||||
)
|
||||
}
|
||||
addPage(
|
||||
page = SettingsPage.Homescreen,
|
||||
key = "home-layout",
|
||||
title = homeLayoutPage,
|
||||
description = stringResource(Res.string.settings_content_discovery_homescreen_description),
|
||||
icon = Icons.Rounded.Home,
|
||||
)
|
||||
addPage(
|
||||
page = SettingsPage.MetaScreen,
|
||||
key = "detail-page",
|
||||
title = detailPage,
|
||||
description = stringResource(Res.string.settings_content_discovery_meta_screen_description),
|
||||
icon = Icons.Rounded.Tune,
|
||||
)
|
||||
add(
|
||||
key = "collections",
|
||||
title = collectionsPage,
|
||||
description = stringResource(Res.string.settings_content_discovery_collections_description),
|
||||
page = contentDiscoveryPage,
|
||||
section = stringResource(Res.string.settings_content_discovery_section_home),
|
||||
category = generalCategory,
|
||||
icon = Icons.Rounded.CollectionsBookmark,
|
||||
target = SettingsSearchTarget.Collections,
|
||||
)
|
||||
|
||||
val playbackPlayer = stringResource(Res.string.settings_playback_section_player)
|
||||
val playbackSubtitleAudio = stringResource(Res.string.settings_playback_section_subtitle_audio)
|
||||
val playbackStreamSelection = stringResource(Res.string.settings_playback_section_stream_selection)
|
||||
val playbackStreamAutoPlay = stringResource(Res.string.settings_playback_section_stream_auto_play)
|
||||
val playbackDecoder = stringResource(Res.string.settings_playback_section_decoder)
|
||||
val playbackSubtitleRendering = stringResource(Res.string.settings_playback_section_subtitle_rendering)
|
||||
val playbackSkipSegments = stringResource(Res.string.settings_playback_section_skip_segments)
|
||||
val playbackNextEpisode = stringResource(Res.string.settings_playback_section_next_episode)
|
||||
addPlaybackRows(
|
||||
addRow = ::addRow,
|
||||
pageLabel = playbackPage,
|
||||
section = playbackPlayer,
|
||||
icon = Icons.Rounded.PlayArrow,
|
||||
rows = listOf(
|
||||
PlaybackSearchRow(
|
||||
"loading-overlay",
|
||||
stringResource(Res.string.settings_playback_show_loading_overlay),
|
||||
stringResource(Res.string.settings_playback_show_loading_overlay_description),
|
||||
),
|
||||
PlaybackSearchRow(
|
||||
"hold-to-speed",
|
||||
stringResource(Res.string.settings_playback_hold_to_speed),
|
||||
stringResource(Res.string.settings_playback_hold_to_speed_description),
|
||||
),
|
||||
PlaybackSearchRow("hold-speed", stringResource(Res.string.settings_playback_hold_speed)),
|
||||
),
|
||||
)
|
||||
addPlaybackRows(
|
||||
addRow = ::addRow,
|
||||
pageLabel = playbackPage,
|
||||
section = playbackSubtitleAudio,
|
||||
icon = Icons.Rounded.PlayArrow,
|
||||
rows = listOf(
|
||||
PlaybackSearchRow("preferred-audio", stringResource(Res.string.settings_playback_preferred_audio_language)),
|
||||
PlaybackSearchRow("secondary-audio", stringResource(Res.string.settings_playback_secondary_audio_language)),
|
||||
PlaybackSearchRow("preferred-subtitles", stringResource(Res.string.settings_playback_preferred_subtitle_language)),
|
||||
PlaybackSearchRow("secondary-subtitles", stringResource(Res.string.settings_playback_secondary_subtitle_language)),
|
||||
),
|
||||
)
|
||||
addPlaybackRows(
|
||||
addRow = ::addRow,
|
||||
pageLabel = playbackPage,
|
||||
section = playbackStreamSelection,
|
||||
icon = Icons.Rounded.PlayArrow,
|
||||
rows = listOf(
|
||||
PlaybackSearchRow(
|
||||
"reuse-last-link",
|
||||
stringResource(Res.string.settings_playback_reuse_last_link),
|
||||
stringResource(Res.string.settings_playback_reuse_last_link_description),
|
||||
),
|
||||
PlaybackSearchRow("last-link-cache", stringResource(Res.string.settings_playback_last_link_cache_duration)),
|
||||
),
|
||||
)
|
||||
addPlaybackRows(
|
||||
addRow = ::addRow,
|
||||
pageLabel = playbackPage,
|
||||
section = playbackStreamAutoPlay,
|
||||
icon = Icons.Rounded.PlayArrow,
|
||||
rows = buildList {
|
||||
add(PlaybackSearchRow("stream-mode", stringResource(Res.string.settings_playback_stream_selection_mode)))
|
||||
add(PlaybackSearchRow("regex-pattern", stringResource(Res.string.settings_playback_regex_pattern)))
|
||||
add(PlaybackSearchRow("stream-timeout", stringResource(Res.string.settings_playback_stream_timeout), stringResource(Res.string.settings_playback_stream_timeout_description)))
|
||||
add(PlaybackSearchRow("source-scope", stringResource(Res.string.settings_playback_source_scope)))
|
||||
add(PlaybackSearchRow("allowed-addons", stringResource(Res.string.settings_playback_allowed_addons)))
|
||||
if (pluginsEnabled) add(PlaybackSearchRow("allowed-plugins", stringResource(Res.string.settings_playback_allowed_plugins)))
|
||||
},
|
||||
)
|
||||
if (!isIos) {
|
||||
addPlaybackRows(
|
||||
addRow = ::addRow,
|
||||
pageLabel = playbackPage,
|
||||
section = playbackDecoder,
|
||||
icon = Icons.Rounded.PlayArrow,
|
||||
rows = listOf(
|
||||
PlaybackSearchRow("decoder-priority", stringResource(Res.string.settings_playback_decoder_priority)),
|
||||
PlaybackSearchRow("dv7-hevc", stringResource(Res.string.settings_playback_map_dv7_to_hevc), stringResource(Res.string.settings_playback_map_dv7_to_hevc_description)),
|
||||
PlaybackSearchRow("tunneled-playback", stringResource(Res.string.settings_playback_tunneled_playback), stringResource(Res.string.settings_playback_tunneled_playback_description)),
|
||||
),
|
||||
)
|
||||
addPlaybackRows(
|
||||
addRow = ::addRow,
|
||||
pageLabel = playbackPage,
|
||||
section = playbackSubtitleRendering,
|
||||
icon = Icons.Rounded.PlayArrow,
|
||||
rows = listOf(
|
||||
PlaybackSearchRow("libass", stringResource(Res.string.settings_playback_enable_libass), stringResource(Res.string.settings_playback_enable_libass_description)),
|
||||
PlaybackSearchRow("libass-render", stringResource(Res.string.settings_playback_render_type)),
|
||||
),
|
||||
)
|
||||
}
|
||||
addPlaybackRows(
|
||||
addRow = ::addRow,
|
||||
pageLabel = playbackPage,
|
||||
section = playbackSkipSegments,
|
||||
icon = Icons.Rounded.PlayArrow,
|
||||
rows = listOf(
|
||||
PlaybackSearchRow("skip-intro", stringResource(Res.string.settings_playback_skip_intro_outro_recap), stringResource(Res.string.settings_playback_skip_intro_outro_recap_description)),
|
||||
PlaybackSearchRow("anime-skip", stringResource(Res.string.settings_playback_anime_skip), stringResource(Res.string.settings_playback_anime_skip_description)),
|
||||
PlaybackSearchRow("anime-skip-client", stringResource(Res.string.settings_playback_anime_skip_client_id), stringResource(Res.string.settings_playback_anime_skip_client_id_description)),
|
||||
PlaybackSearchRow("intro-submit", stringResource(Res.string.settings_playback_intro_submit_enabled), stringResource(Res.string.settings_playback_intro_submit_enabled_description)),
|
||||
PlaybackSearchRow("introdb-key", stringResource(Res.string.settings_playback_introdb_api_key), stringResource(Res.string.settings_playback_introdb_api_key_description)),
|
||||
),
|
||||
)
|
||||
addPlaybackRows(
|
||||
addRow = ::addRow,
|
||||
pageLabel = playbackPage,
|
||||
section = playbackNextEpisode,
|
||||
icon = Icons.Rounded.PlayArrow,
|
||||
rows = listOf(
|
||||
PlaybackSearchRow("auto-play-next", stringResource(Res.string.settings_playback_auto_play_next_episode), stringResource(Res.string.settings_playback_auto_play_next_episode_description)),
|
||||
PlaybackSearchRow("prefer-binge", stringResource(Res.string.settings_playback_prefer_binge_group), stringResource(Res.string.settings_playback_prefer_binge_group_description)),
|
||||
PlaybackSearchRow("threshold-mode", stringResource(Res.string.settings_playback_threshold_mode)),
|
||||
PlaybackSearchRow("threshold-percent", stringResource(Res.string.settings_playback_threshold_percentage), stringResource(Res.string.settings_playback_threshold_percentage_description)),
|
||||
PlaybackSearchRow("threshold-minutes", stringResource(Res.string.settings_playback_minutes_before_end), stringResource(Res.string.settings_playback_minutes_before_end_description)),
|
||||
),
|
||||
)
|
||||
|
||||
addContinueWatchingRows(
|
||||
addRow = ::addRow,
|
||||
pageLabel = continueWatchingPage,
|
||||
section = stringResource(Res.string.settings_continue_watching_section_visibility),
|
||||
icon = Icons.Rounded.Style,
|
||||
rows = listOf(
|
||||
PlaybackSearchRow(
|
||||
"show-continue-watching",
|
||||
stringResource(Res.string.settings_continue_watching_show_title),
|
||||
stringResource(Res.string.settings_continue_watching_show_description),
|
||||
),
|
||||
),
|
||||
)
|
||||
addContinueWatchingRows(
|
||||
addRow = ::addRow,
|
||||
pageLabel = continueWatchingPage,
|
||||
section = stringResource(Res.string.settings_continue_watching_section_up_next_behavior),
|
||||
icon = Icons.Rounded.Style,
|
||||
rows = listOf(
|
||||
PlaybackSearchRow("episode-thumbnails", stringResource(Res.string.settings_continue_watching_use_episode_thumbnails_title), stringResource(Res.string.settings_continue_watching_use_episode_thumbnails_description)),
|
||||
PlaybackSearchRow("up-next", stringResource(Res.string.settings_continue_watching_up_next_title), stringResource(Res.string.settings_continue_watching_up_next_description)),
|
||||
PlaybackSearchRow("unaired-next-up", stringResource(Res.string.settings_continue_watching_show_unaired_next_up_title), stringResource(Res.string.settings_continue_watching_show_unaired_next_up_description)),
|
||||
PlaybackSearchRow("blur-next-up", stringResource(Res.string.settings_continue_watching_blur_next_up_title), stringResource(Res.string.settings_continue_watching_blur_next_up_description)),
|
||||
),
|
||||
)
|
||||
addContinueWatchingRows(
|
||||
addRow = ::addRow,
|
||||
pageLabel = continueWatchingPage,
|
||||
section = stringResource(Res.string.settings_continue_watching_section_on_launch),
|
||||
icon = Icons.Rounded.Style,
|
||||
rows = listOf(
|
||||
PlaybackSearchRow("resume-prompt", stringResource(Res.string.settings_continue_watching_resume_prompt_title), stringResource(Res.string.settings_continue_watching_resume_prompt_description)),
|
||||
),
|
||||
)
|
||||
|
||||
val posterSection = stringResource(Res.string.settings_poster_card_style)
|
||||
listOf(
|
||||
PlaybackSearchRow("poster-width", stringResource(Res.string.settings_poster_card_width)),
|
||||
PlaybackSearchRow("poster-radius", stringResource(Res.string.settings_poster_card_radius)),
|
||||
PlaybackSearchRow("poster-landscape", stringResource(Res.string.settings_poster_landscape_mode)),
|
||||
PlaybackSearchRow("poster-hide-labels", stringResource(Res.string.settings_poster_hide_labels)),
|
||||
).forEach { row ->
|
||||
addRow(
|
||||
page = SettingsPage.PosterCustomization,
|
||||
key = "poster-${row.key}",
|
||||
title = row.title,
|
||||
description = row.description,
|
||||
pageLabel = posterStylePage,
|
||||
section = posterSection,
|
||||
icon = Icons.Rounded.Tune,
|
||||
)
|
||||
}
|
||||
|
||||
val homeLayoutSection = stringResource(Res.string.settings_homescreen_section_hero)
|
||||
listOf(
|
||||
PlaybackSearchRow("home-hero", stringResource(Res.string.settings_homescreen_show_hero), stringResource(Res.string.settings_homescreen_show_hero_description)),
|
||||
PlaybackSearchRow("home-hide-unreleased", stringResource(Res.string.layout_hide_unreleased), stringResource(Res.string.layout_hide_unreleased_sub)),
|
||||
PlaybackSearchRow("home-hero-sources", stringResource(Res.string.settings_homescreen_section_hero_sources)),
|
||||
PlaybackSearchRow("home-catalogs", stringResource(Res.string.settings_homescreen_section_catalogs)),
|
||||
).forEach { row ->
|
||||
addRow(
|
||||
page = SettingsPage.Homescreen,
|
||||
key = row.key,
|
||||
title = row.title,
|
||||
description = row.description,
|
||||
pageLabel = homeLayoutPage,
|
||||
section = homeLayoutSection,
|
||||
icon = Icons.Rounded.Home,
|
||||
)
|
||||
}
|
||||
|
||||
val detailAppearanceSection = stringResource(Res.string.settings_meta_section_appearance)
|
||||
listOf(
|
||||
PlaybackSearchRow("meta-cinematic", stringResource(Res.string.settings_meta_cinematic_background), stringResource(Res.string.settings_meta_cinematic_background_description)),
|
||||
PlaybackSearchRow("meta-tabs", stringResource(Res.string.settings_meta_tab_layout), stringResource(Res.string.settings_meta_tab_layout_description)),
|
||||
PlaybackSearchRow("meta-episode-cards", stringResource(Res.string.settings_meta_episode_cards), stringResource(Res.string.settings_meta_episode_cards_description)),
|
||||
PlaybackSearchRow("meta-blur-episodes", stringResource(Res.string.settings_meta_blur_unwatched_episodes), stringResource(Res.string.settings_meta_blur_unwatched_episodes_description)),
|
||||
).forEach { row ->
|
||||
addRow(
|
||||
page = SettingsPage.MetaScreen,
|
||||
key = row.key,
|
||||
title = row.title,
|
||||
description = row.description,
|
||||
pageLabel = detailPage,
|
||||
section = detailAppearanceSection,
|
||||
icon = Icons.Rounded.Tune,
|
||||
)
|
||||
}
|
||||
val detailSectionsSection = stringResource(Res.string.settings_meta_section_sections)
|
||||
listOf(
|
||||
PlaybackSearchRow("meta-overview", stringResource(Res.string.settings_meta_overview), stringResource(Res.string.settings_meta_overview_description)),
|
||||
PlaybackSearchRow("meta-actions", stringResource(Res.string.settings_meta_actions), stringResource(Res.string.settings_meta_actions_description)),
|
||||
PlaybackSearchRow("meta-details", stringResource(Res.string.settings_meta_details), stringResource(Res.string.settings_meta_details_description)),
|
||||
PlaybackSearchRow("meta-trailers", stringResource(Res.string.settings_meta_trailers), stringResource(Res.string.settings_meta_trailers_description)),
|
||||
PlaybackSearchRow("meta-cast", stringResource(Res.string.settings_meta_cast), stringResource(Res.string.settings_meta_cast_description)),
|
||||
PlaybackSearchRow("meta-episodes", stringResource(Res.string.settings_meta_episodes), stringResource(Res.string.settings_meta_episodes_description)),
|
||||
PlaybackSearchRow("meta-production", stringResource(Res.string.settings_meta_production), stringResource(Res.string.settings_meta_production_description)),
|
||||
PlaybackSearchRow("meta-more-like-this", stringResource(Res.string.settings_meta_more_like_this), stringResource(Res.string.settings_meta_more_like_this_description)),
|
||||
PlaybackSearchRow("meta-collection", stringResource(Res.string.settings_meta_collection), stringResource(Res.string.settings_meta_collection_description)),
|
||||
PlaybackSearchRow("meta-comments", stringResource(Res.string.settings_meta_comments), stringResource(Res.string.settings_meta_comments_description)),
|
||||
).forEach { row ->
|
||||
addRow(
|
||||
page = SettingsPage.MetaScreen,
|
||||
key = row.key,
|
||||
title = row.title,
|
||||
description = row.description,
|
||||
pageLabel = detailPage,
|
||||
section = detailSectionsSection,
|
||||
icon = Icons.Rounded.Tune,
|
||||
)
|
||||
}
|
||||
|
||||
addPage(
|
||||
page = SettingsPage.TmdbEnrichment,
|
||||
key = "tmdb",
|
||||
title = tmdbPage,
|
||||
description = stringResource(Res.string.settings_integrations_tmdb_description),
|
||||
icon = Icons.Rounded.Link,
|
||||
)
|
||||
addPage(
|
||||
page = SettingsPage.MdbListRatings,
|
||||
key = "mdblist",
|
||||
title = mdbListPage,
|
||||
description = stringResource(Res.string.settings_integrations_mdblist_description),
|
||||
icon = Icons.Rounded.Link,
|
||||
)
|
||||
val tmdbModulesSection = stringResource(Res.string.settings_tmdb_section_modules)
|
||||
listOf(
|
||||
PlaybackSearchRow("tmdb-enable", stringResource(Res.string.settings_tmdb_enable_enrichment), stringResource(Res.string.settings_tmdb_enable_enrichment_description), stringResource(Res.string.settings_tmdb_section_title)),
|
||||
PlaybackSearchRow("tmdb-api-key", stringResource(Res.string.settings_tmdb_personal_api_key), "", stringResource(Res.string.settings_tmdb_section_credentials)),
|
||||
PlaybackSearchRow("tmdb-language", stringResource(Res.string.settings_tmdb_preferred_language), stringResource(Res.string.settings_tmdb_preferred_language_description), stringResource(Res.string.settings_tmdb_section_localization)),
|
||||
PlaybackSearchRow("tmdb-trailers", stringResource(Res.string.settings_tmdb_module_trailers), stringResource(Res.string.settings_tmdb_module_trailers_description), tmdbModulesSection),
|
||||
PlaybackSearchRow("tmdb-artwork", stringResource(Res.string.settings_tmdb_module_artwork), stringResource(Res.string.settings_tmdb_module_artwork_description), tmdbModulesSection),
|
||||
PlaybackSearchRow("tmdb-basic-info", stringResource(Res.string.settings_tmdb_module_basic_info), stringResource(Res.string.settings_tmdb_module_basic_info_description), tmdbModulesSection),
|
||||
PlaybackSearchRow("tmdb-details", stringResource(Res.string.settings_tmdb_module_details), stringResource(Res.string.settings_tmdb_module_details_description), tmdbModulesSection),
|
||||
PlaybackSearchRow("tmdb-credits", stringResource(Res.string.settings_tmdb_module_credits), stringResource(Res.string.settings_tmdb_module_credits_description), tmdbModulesSection),
|
||||
PlaybackSearchRow("tmdb-companies", stringResource(Res.string.settings_tmdb_module_production_companies), stringResource(Res.string.settings_tmdb_module_production_companies_description), tmdbModulesSection),
|
||||
PlaybackSearchRow("tmdb-networks", stringResource(Res.string.settings_tmdb_module_networks), stringResource(Res.string.settings_tmdb_module_networks_description), tmdbModulesSection),
|
||||
PlaybackSearchRow("tmdb-episodes", stringResource(Res.string.settings_tmdb_module_episodes), stringResource(Res.string.settings_tmdb_module_episodes_description), tmdbModulesSection),
|
||||
PlaybackSearchRow("tmdb-season-posters", stringResource(Res.string.settings_tmdb_module_season_posters), stringResource(Res.string.settings_tmdb_module_season_posters_description), tmdbModulesSection),
|
||||
PlaybackSearchRow("tmdb-more-like-this", stringResource(Res.string.settings_tmdb_module_more_like_this), stringResource(Res.string.settings_tmdb_module_more_like_this_description), tmdbModulesSection),
|
||||
PlaybackSearchRow("tmdb-collections", stringResource(Res.string.settings_tmdb_module_collections), stringResource(Res.string.settings_tmdb_module_collections_description), tmdbModulesSection),
|
||||
).forEach { row ->
|
||||
addRow(
|
||||
page = SettingsPage.TmdbEnrichment,
|
||||
key = row.key,
|
||||
title = row.title,
|
||||
description = row.description,
|
||||
pageLabel = tmdbPage,
|
||||
section = row.sectionOverride ?: tmdbModulesSection,
|
||||
icon = Icons.Rounded.Link,
|
||||
)
|
||||
}
|
||||
|
||||
listOf(
|
||||
PlaybackSearchRow("mdb-enable", stringResource(Res.string.settings_mdb_enable_ratings), stringResource(Res.string.settings_mdb_enable_ratings_description), stringResource(Res.string.settings_mdb_section_title)),
|
||||
PlaybackSearchRow("mdb-api-key", stringResource(Res.string.settings_mdb_api_key_title), stringResource(Res.string.settings_mdb_api_key_description), stringResource(Res.string.settings_mdb_section_api_key)),
|
||||
PlaybackSearchRow("mdb-imdb", stringResource(Res.string.source_imdb), "", stringResource(Res.string.settings_mdb_section_rating_providers)),
|
||||
PlaybackSearchRow("mdb-tmdb", stringResource(Res.string.source_tmdb), "", stringResource(Res.string.settings_mdb_section_rating_providers)),
|
||||
PlaybackSearchRow("mdb-tomatoes", stringResource(Res.string.source_rotten_tomatoes), "", stringResource(Res.string.settings_mdb_section_rating_providers)),
|
||||
PlaybackSearchRow("mdb-metacritic", stringResource(Res.string.source_metacritic), "", stringResource(Res.string.settings_mdb_section_rating_providers)),
|
||||
PlaybackSearchRow("mdb-trakt", stringResource(Res.string.source_trakt), "", stringResource(Res.string.settings_mdb_section_rating_providers)),
|
||||
PlaybackSearchRow("mdb-letterboxd", stringResource(Res.string.source_letterboxd), "", stringResource(Res.string.settings_mdb_section_rating_providers)),
|
||||
PlaybackSearchRow("mdb-audience", stringResource(Res.string.source_audience_score), "", stringResource(Res.string.settings_mdb_section_rating_providers)),
|
||||
).forEach { row ->
|
||||
addRow(
|
||||
page = SettingsPage.MdbListRatings,
|
||||
key = row.key,
|
||||
title = row.title,
|
||||
description = row.description,
|
||||
pageLabel = mdbListPage,
|
||||
section = row.sectionOverride ?: stringResource(Res.string.settings_mdb_section_title),
|
||||
icon = Icons.Rounded.Link,
|
||||
)
|
||||
}
|
||||
|
||||
val notificationsAlerts = stringResource(Res.string.settings_notifications_section_alerts)
|
||||
addRow(
|
||||
page = SettingsPage.Notifications,
|
||||
key = "episode-release-alerts",
|
||||
title = stringResource(Res.string.settings_notifications_episode_release_alerts),
|
||||
description = stringResource(Res.string.settings_notifications_episode_release_alerts_description),
|
||||
pageLabel = notificationsPage,
|
||||
section = notificationsAlerts,
|
||||
icon = Icons.Rounded.Notifications,
|
||||
)
|
||||
addRow(
|
||||
page = SettingsPage.Notifications,
|
||||
key = "notification-test",
|
||||
title = stringResource(Res.string.settings_notifications_test_title),
|
||||
pageLabel = notificationsPage,
|
||||
section = stringResource(Res.string.settings_notifications_section_test),
|
||||
icon = Icons.Rounded.Notifications,
|
||||
)
|
||||
|
||||
addRow(
|
||||
page = SettingsPage.TraktAuthentication,
|
||||
key = "trakt-authentication",
|
||||
title = stringResource(Res.string.settings_trakt_authentication),
|
||||
description = stringResource(Res.string.settings_trakt_intro_description),
|
||||
pageLabel = traktPage,
|
||||
section = stringResource(Res.string.settings_trakt_authentication),
|
||||
category = accountCategory,
|
||||
icon = Icons.Rounded.Link,
|
||||
)
|
||||
listOf(
|
||||
PlaybackSearchRow("trakt-library-source", stringResource(Res.string.trakt_library_source_title), stringResource(Res.string.trakt_library_source_subtitle)),
|
||||
PlaybackSearchRow("trakt-watch-progress", stringResource(Res.string.trakt_watch_progress_title), stringResource(Res.string.trakt_watch_progress_subtitle)),
|
||||
PlaybackSearchRow("trakt-continue-watching-window", stringResource(Res.string.trakt_continue_watching_window), stringResource(Res.string.trakt_continue_watching_subtitle)),
|
||||
PlaybackSearchRow("trakt-comments", stringResource(Res.string.settings_trakt_comments), stringResource(Res.string.settings_trakt_comments_description)),
|
||||
).forEach { row ->
|
||||
addRow(
|
||||
page = SettingsPage.TraktAuthentication,
|
||||
key = row.key,
|
||||
title = row.title,
|
||||
description = row.description,
|
||||
pageLabel = traktPage,
|
||||
section = stringResource(Res.string.settings_trakt_features),
|
||||
category = accountCategory,
|
||||
icon = Icons.Rounded.Link,
|
||||
)
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
private data class PlaybackSearchRow(
|
||||
val key: String,
|
||||
val title: String,
|
||||
val description: String = "",
|
||||
val sectionOverride: String? = null,
|
||||
)
|
||||
|
||||
private fun addPlaybackRows(
|
||||
addRow: (
|
||||
page: SettingsPage,
|
||||
key: String,
|
||||
title: String,
|
||||
description: String,
|
||||
pageLabel: String,
|
||||
section: String,
|
||||
category: String,
|
||||
icon: ImageVector,
|
||||
) -> Unit,
|
||||
pageLabel: String,
|
||||
section: String,
|
||||
icon: ImageVector,
|
||||
rows: List<PlaybackSearchRow>,
|
||||
) {
|
||||
rows.forEach { row ->
|
||||
addRow(
|
||||
SettingsPage.Playback,
|
||||
"playback-${row.key}",
|
||||
row.title,
|
||||
row.description,
|
||||
pageLabel,
|
||||
section,
|
||||
"",
|
||||
icon,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addContinueWatchingRows(
|
||||
addRow: (
|
||||
page: SettingsPage,
|
||||
key: String,
|
||||
title: String,
|
||||
description: String,
|
||||
pageLabel: String,
|
||||
section: String,
|
||||
category: String,
|
||||
icon: ImageVector,
|
||||
) -> Unit,
|
||||
pageLabel: String,
|
||||
section: String,
|
||||
icon: ImageVector,
|
||||
rows: List<PlaybackSearchRow>,
|
||||
) {
|
||||
rows.forEach { row ->
|
||||
addRow(
|
||||
SettingsPage.ContinueWatching,
|
||||
"continue-watching-${row.key}",
|
||||
row.title,
|
||||
row.description,
|
||||
pageLabel,
|
||||
section,
|
||||
"",
|
||||
icon,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun LazyListScope.settingsSearchRootContent(
|
||||
query: String,
|
||||
entries: List<SettingsSearchEntry>,
|
||||
isTablet: Boolean,
|
||||
showSearchField: Boolean,
|
||||
animateSearchField: Boolean,
|
||||
onQueryChange: (String) -> Unit,
|
||||
onTargetClick: (SettingsSearchTarget) -> Unit,
|
||||
) {
|
||||
if (showSearchField || query.isNotBlank()) {
|
||||
item(key = "settings-search-field") {
|
||||
SettingsSearchRevealItem(animate = animateSearchField) {
|
||||
SettingsSearchField(
|
||||
query = query,
|
||||
onQueryChange = onQueryChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (query.isBlank()) return
|
||||
|
||||
val results = settingsSearchResults(
|
||||
query = query,
|
||||
entries = entries,
|
||||
)
|
||||
|
||||
item(key = "settings-search-results") {
|
||||
if (results.isEmpty()) {
|
||||
SettingsSearchEmptyState(isTablet = isTablet)
|
||||
} else {
|
||||
SettingsSection(
|
||||
title = stringResource(Res.string.settings_search_results_section),
|
||||
isTablet = isTablet,
|
||||
) {
|
||||
SettingsGroup(isTablet = isTablet) {
|
||||
results.forEachIndexed { index, entry ->
|
||||
if (index > 0) {
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
}
|
||||
SettingsNavigationRow(
|
||||
title = entry.title,
|
||||
description = entry.resultDescription(),
|
||||
icon = entry.icon,
|
||||
isTablet = isTablet,
|
||||
onClick = { onTargetClick(entry.target) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsSearchRevealItem(
|
||||
animate: Boolean,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
if (!animate) {
|
||||
content()
|
||||
return
|
||||
}
|
||||
|
||||
val visibleState = remember {
|
||||
MutableTransitionState(false).apply {
|
||||
targetState = true
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visibleState = visibleState,
|
||||
enter = expandVertically(
|
||||
animationSpec = tween(durationMillis = 220),
|
||||
expandFrom = Alignment.Top,
|
||||
) + fadeIn(
|
||||
animationSpec = tween(durationMillis = 180),
|
||||
) + slideInVertically(
|
||||
animationSpec = tween(durationMillis = 220),
|
||||
initialOffsetY = { -it / 4 },
|
||||
),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsSearchField(
|
||||
query: String,
|
||||
onQueryChange: (String) -> Unit,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = query,
|
||||
onValueChange = onQueryChange,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Search,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
},
|
||||
trailingIcon = if (query.isNotBlank()) {
|
||||
{
|
||||
IconButton(onClick = { onQueryChange("") }) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Close,
|
||||
contentDescription = stringResource(Res.string.compose_search_clear),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(Res.string.settings_search_placeholder),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
},
|
||||
textStyle = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = MaterialTheme.colorScheme.outline,
|
||||
unfocusedBorderColor = MaterialTheme.colorScheme.outline,
|
||||
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
cursorColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsSearchEmptyState(isTablet: Boolean) {
|
||||
SettingsSection(
|
||||
title = stringResource(Res.string.settings_search_results_section),
|
||||
isTablet = isTablet,
|
||||
) {
|
||||
SettingsGroup(isTablet = isTablet) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = if (isTablet) 20.dp else 16.dp, vertical = 18.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.settings_search_empty),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun settingsSearchResults(
|
||||
query: String,
|
||||
entries: List<SettingsSearchEntry>,
|
||||
): List<SettingsSearchEntry> {
|
||||
val terms = query
|
||||
.trim()
|
||||
.lowercase()
|
||||
.split(Regex("\\s+"))
|
||||
.filter { it.isNotBlank() }
|
||||
|
||||
if (terms.isEmpty()) return emptyList()
|
||||
|
||||
return entries.filter { entry ->
|
||||
terms.all { term -> entry.searchableText.contains(term) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun SettingsSearchEntry.resultDescription(): String {
|
||||
return description.ifBlank { contextLabel }
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
CURRENT_PROJECT_VERSION=55
|
||||
MARKETING_VERSION=0.1.16
|
||||
MARKETING_VERSION=0.1.0
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue