mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-17 07:21:58 +00:00
Merge branch 'cmp-rewrite' of https://github.com/NuvioMedia/NuvioMobile into cmp-rewrite
This commit is contained in:
commit
46a82dce9a
14 changed files with 1487 additions and 4 deletions
|
|
@ -260,6 +260,7 @@ kotlin {
|
||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
implementation(libs.coil.network.ktor3)
|
implementation(libs.coil.network.ktor3)
|
||||||
|
implementation(libs.coil.svg)
|
||||||
implementation("dev.chrisbanes.haze:haze:1.7.2")
|
implementation("dev.chrisbanes.haze:haze:1.7.2")
|
||||||
implementation(libs.compose.runtime)
|
implementation(libs.compose.runtime)
|
||||||
implementation(libs.compose.foundation)
|
implementation(libs.compose.foundation)
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,5 @@
|
||||||
<locale android:name="el"/>
|
<locale android:name="el"/>
|
||||||
<locale android:name="pl"/>
|
<locale android:name="pl"/>
|
||||||
<locale android:name="de"/>
|
<locale android:name="de"/>
|
||||||
|
<locale android:name="cs"/>
|
||||||
</locale-config>
|
</locale-config>
|
||||||
|
|
|
||||||
1245
composeApp/src/commonMain/composeResources/values-cs/strings.xml
Normal file
1245
composeApp/src/commonMain/composeResources/values-cs/strings.xml
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -541,6 +541,12 @@
|
||||||
<string name="settings_continue_watching_blur_next_up_title">Blur Unwatched in Continue Watching</string>
|
<string name="settings_continue_watching_blur_next_up_title">Blur Unwatched in Continue Watching</string>
|
||||||
<string name="settings_continue_watching_show_unaired_next_up_description">Include upcoming episodes in Continue Watching before they air.</string>
|
<string name="settings_continue_watching_show_unaired_next_up_description">Include upcoming episodes in Continue Watching before they air.</string>
|
||||||
<string name="settings_continue_watching_show_unaired_next_up_title">Show Unaired Next Up Episodes</string>
|
<string name="settings_continue_watching_show_unaired_next_up_title">Show Unaired Next Up Episodes</string>
|
||||||
|
<string name="settings_continue_watching_section_sort_order">SORT ORDER</string>
|
||||||
|
<string name="settings_continue_watching_sort_mode_title">Sort Order</string>
|
||||||
|
<string name="settings_continue_watching_sort_mode_default">Default</string>
|
||||||
|
<string name="settings_continue_watching_sort_mode_default_desc">Sort all items by recency</string>
|
||||||
|
<string name="settings_continue_watching_sort_mode_streaming">Streaming Style</string>
|
||||||
|
<string name="settings_continue_watching_sort_mode_streaming_desc">Released items first, upcoming at the end</string>
|
||||||
<string name="settings_continue_watching_section_card_style">Poster Card Style</string>
|
<string name="settings_continue_watching_section_card_style">Poster Card Style</string>
|
||||||
<string name="settings_continue_watching_section_on_launch">ON LAUNCH</string>
|
<string name="settings_continue_watching_section_on_launch">ON LAUNCH</string>
|
||||||
<string name="settings_continue_watching_section_up_next_behavior">UP NEXT BEHAVIOR</string>
|
<string name="settings_continue_watching_section_up_next_behavior">UP NEXT BEHAVIOR</string>
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ import coil3.ImageLoader
|
||||||
import coil3.compose.setSingletonImageLoaderFactory
|
import coil3.compose.setSingletonImageLoaderFactory
|
||||||
import coil3.request.CachePolicy
|
import coil3.request.CachePolicy
|
||||||
import coil3.request.crossfade
|
import coil3.request.crossfade
|
||||||
|
import coil3.svg.SvgDecoder
|
||||||
import com.nuvio.app.core.build.AppFeaturePolicy
|
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||||
import com.nuvio.app.core.auth.AuthRepository
|
import com.nuvio.app.core.auth.AuthRepository
|
||||||
import com.nuvio.app.core.auth.AuthState
|
import com.nuvio.app.core.auth.AuthState
|
||||||
|
|
@ -304,6 +305,9 @@ fun App() {
|
||||||
.crossfade(true)
|
.crossfade(true)
|
||||||
.diskCachePolicy(CachePolicy.ENABLED)
|
.diskCachePolicy(CachePolicy.ENABLED)
|
||||||
.memoryCachePolicy(CachePolicy.ENABLED)
|
.memoryCachePolicy(CachePolicy.ENABLED)
|
||||||
|
.components {
|
||||||
|
add(SvgDecoder.Factory())
|
||||||
|
}
|
||||||
.configurePlatformImageLoader()
|
.configurePlatformImageLoader()
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import com.nuvio.app.features.watchprogress.ContinueWatchingEnrichmentCache
|
||||||
import com.nuvio.app.features.watchprogress.CurrentDateProvider
|
import com.nuvio.app.features.watchprogress.CurrentDateProvider
|
||||||
import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepository
|
import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepository
|
||||||
import com.nuvio.app.features.watchprogress.ContinueWatchingItem
|
import com.nuvio.app.features.watchprogress.ContinueWatchingItem
|
||||||
|
import com.nuvio.app.features.watchprogress.ContinueWatchingSortMode
|
||||||
import com.nuvio.app.features.watchprogress.isSeriesTypeForContinueWatching
|
import com.nuvio.app.features.watchprogress.isSeriesTypeForContinueWatching
|
||||||
import com.nuvio.app.features.watchprogress.nextUpDismissKey
|
import com.nuvio.app.features.watchprogress.nextUpDismissKey
|
||||||
import com.nuvio.app.features.watchprogress.WatchProgressClock
|
import com.nuvio.app.features.watchprogress.WatchProgressClock
|
||||||
|
|
@ -247,11 +248,14 @@ fun HomeScreen(
|
||||||
visibleContinueWatchingEntries,
|
visibleContinueWatchingEntries,
|
||||||
cachedInProgressItems,
|
cachedInProgressItems,
|
||||||
effectivNextUpItems,
|
effectivNextUpItems,
|
||||||
|
continueWatchingPreferences.sortMode,
|
||||||
) {
|
) {
|
||||||
buildHomeContinueWatchingItems(
|
buildHomeContinueWatchingItems(
|
||||||
visibleEntries = visibleContinueWatchingEntries,
|
visibleEntries = visibleContinueWatchingEntries,
|
||||||
cachedInProgressByVideoId = cachedInProgressItems,
|
cachedInProgressByVideoId = cachedInProgressItems,
|
||||||
nextUpItemsBySeries = effectivNextUpItems,
|
nextUpItemsBySeries = effectivNextUpItems,
|
||||||
|
sortMode = continueWatchingPreferences.sortMode,
|
||||||
|
todayIsoDate = CurrentDateProvider.todayIsoDate(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val availableManifests = remember(addonsUiState.addons) {
|
val availableManifests = remember(addonsUiState.addons) {
|
||||||
|
|
@ -639,6 +643,8 @@ internal fun buildHomeContinueWatchingItems(
|
||||||
visibleEntries: List<WatchProgressEntry>,
|
visibleEntries: List<WatchProgressEntry>,
|
||||||
cachedInProgressByVideoId: Map<String, ContinueWatchingItem> = emptyMap(),
|
cachedInProgressByVideoId: Map<String, ContinueWatchingItem> = emptyMap(),
|
||||||
nextUpItemsBySeries: Map<String, Pair<Long, ContinueWatchingItem>>,
|
nextUpItemsBySeries: Map<String, Pair<Long, ContinueWatchingItem>>,
|
||||||
|
sortMode: ContinueWatchingSortMode = ContinueWatchingSortMode.DEFAULT,
|
||||||
|
todayIsoDate: String = "",
|
||||||
): List<ContinueWatchingItem> {
|
): List<ContinueWatchingItem> {
|
||||||
val inProgressSeriesIds = visibleEntries
|
val inProgressSeriesIds = visibleEntries
|
||||||
.asSequence()
|
.asSequence()
|
||||||
|
|
@ -647,7 +653,7 @@ internal fun buildHomeContinueWatchingItems(
|
||||||
.filter(String::isNotBlank)
|
.filter(String::isNotBlank)
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|
||||||
return buildList {
|
val candidates = buildList {
|
||||||
addAll(
|
addAll(
|
||||||
visibleEntries.map { entry ->
|
visibleEntries.map { entry ->
|
||||||
val liveItem = entry.toContinueWatchingItem()
|
val liveItem = entry.toContinueWatchingItem()
|
||||||
|
|
@ -669,13 +675,62 @@ internal fun buildHomeContinueWatchingItems(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deduplicate by series/content id first (order-stable)
|
||||||
|
val seen = mutableSetOf<String>()
|
||||||
|
val deduplicated = candidates
|
||||||
.sortedWith(
|
.sortedWith(
|
||||||
compareByDescending<HomeContinueWatchingCandidate> { it.lastUpdatedEpochMs }
|
compareByDescending<HomeContinueWatchingCandidate> { it.lastUpdatedEpochMs }
|
||||||
.thenByDescending { it.isProgressEntry },
|
.thenByDescending { it.isProgressEntry },
|
||||||
)
|
)
|
||||||
.filter { candidate -> candidate.item.shouldDisplayInContinueWatching() }
|
.filter { candidate -> candidate.item.shouldDisplayInContinueWatching() }
|
||||||
.distinctBy { candidate -> candidate.item.parentMetaId.ifBlank { candidate.item.videoId } }
|
.filter { candidate ->
|
||||||
|
val key = candidate.item.parentMetaId.ifBlank { candidate.item.videoId }
|
||||||
|
seen.add(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (sortMode) {
|
||||||
|
ContinueWatchingSortMode.DEFAULT -> deduplicated.map(HomeContinueWatchingCandidate::item)
|
||||||
|
ContinueWatchingSortMode.STREAMING_STYLE -> applyStreamingStyleSort(deduplicated, todayIsoDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyStreamingStyleSort(
|
||||||
|
candidates: List<HomeContinueWatchingCandidate>,
|
||||||
|
todayIsoDate: String,
|
||||||
|
): List<ContinueWatchingItem> {
|
||||||
|
val (released, unreleased) = candidates.partition { candidate ->
|
||||||
|
val item = candidate.item
|
||||||
|
if (!item.isNextUp) {
|
||||||
|
true // in-progress items are always "released"
|
||||||
|
} else {
|
||||||
|
val itemReleased = item.released
|
||||||
|
if (itemReleased.isNullOrBlank() || todayIsoDate.isBlank()) {
|
||||||
|
true // no date info → treat as released
|
||||||
|
} else {
|
||||||
|
isReleasedBy(todayIsoDate = todayIsoDate, releasedDate = itemReleased)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Released: most recently watched first (already sorted by dedup pass)
|
||||||
|
val sortedReleased = released.map(HomeContinueWatchingCandidate::item)
|
||||||
|
|
||||||
|
// Unaired: soonest air date first; unknown dates go to the end
|
||||||
|
val sortedUnreleased = unreleased
|
||||||
|
.sortedWith { a, b ->
|
||||||
|
val dateA = a.item.released?.takeIf { it.isNotBlank() }
|
||||||
|
val dateB = b.item.released?.takeIf { it.isNotBlank() }
|
||||||
|
when {
|
||||||
|
dateA == null && dateB == null -> 0
|
||||||
|
dateA == null -> 1
|
||||||
|
dateB == null -> -1
|
||||||
|
else -> dateA.compareTo(dateB)
|
||||||
|
}
|
||||||
|
}
|
||||||
.map(HomeContinueWatchingCandidate::item)
|
.map(HomeContinueWatchingCandidate::item)
|
||||||
|
|
||||||
|
return sortedReleased + sortedUnreleased
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class CompletedSeriesCandidate(
|
private data class CompletedSeriesCandidate(
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import nuvio.composeapp.generated.resources.lang_turkish
|
||||||
import nuvio.composeapp.generated.resources.lang_italian
|
import nuvio.composeapp.generated.resources.lang_italian
|
||||||
import nuvio.composeapp.generated.resources.lang_greek
|
import nuvio.composeapp.generated.resources.lang_greek
|
||||||
import nuvio.composeapp.generated.resources.lang_polish
|
import nuvio.composeapp.generated.resources.lang_polish
|
||||||
|
import nuvio.composeapp.generated.resources.lang_czech
|
||||||
import org.jetbrains.compose.resources.StringResource
|
import org.jetbrains.compose.resources.StringResource
|
||||||
|
|
||||||
enum class AppLanguage(
|
enum class AppLanguage(
|
||||||
|
|
@ -25,6 +26,7 @@ enum class AppLanguage(
|
||||||
ITALIAN("it", Res.string.lang_italian),
|
ITALIAN("it", Res.string.lang_italian),
|
||||||
GREEK("el", Res.string.lang_greek),
|
GREEK("el", Res.string.lang_greek),
|
||||||
POLISH("pl", Res.string.lang_polish),
|
POLISH("pl", Res.string.lang_polish),
|
||||||
|
CZECH("cs", Res.string.lang_czech),
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,27 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Check
|
||||||
import androidx.compose.material.icons.rounded.CheckCircle
|
import androidx.compose.material.icons.rounded.CheckCircle
|
||||||
|
import androidx.compose.material3.BasicAlertDialog
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
|
|
@ -25,6 +34,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import com.nuvio.app.features.home.components.ContinueWatchingStylePreview
|
import com.nuvio.app.features.home.components.ContinueWatchingStylePreview
|
||||||
import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepository
|
import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepository
|
||||||
import com.nuvio.app.features.watchprogress.ContinueWatchingSectionStyle
|
import com.nuvio.app.features.watchprogress.ContinueWatchingSectionStyle
|
||||||
|
import com.nuvio.app.features.watchprogress.ContinueWatchingSortMode
|
||||||
import nuvio.composeapp.generated.resources.Res
|
import nuvio.composeapp.generated.resources.Res
|
||||||
import nuvio.composeapp.generated.resources.settings_continue_watching_resume_prompt_description
|
import nuvio.composeapp.generated.resources.settings_continue_watching_resume_prompt_description
|
||||||
import nuvio.composeapp.generated.resources.settings_continue_watching_resume_prompt_title
|
import nuvio.composeapp.generated.resources.settings_continue_watching_resume_prompt_title
|
||||||
|
|
@ -34,10 +44,16 @@ import nuvio.composeapp.generated.resources.settings_continue_watching_show_unai
|
||||||
import nuvio.composeapp.generated.resources.settings_continue_watching_show_unaired_next_up_title
|
import nuvio.composeapp.generated.resources.settings_continue_watching_show_unaired_next_up_title
|
||||||
import nuvio.composeapp.generated.resources.settings_continue_watching_section_card_style
|
import nuvio.composeapp.generated.resources.settings_continue_watching_section_card_style
|
||||||
import nuvio.composeapp.generated.resources.settings_continue_watching_section_on_launch
|
import nuvio.composeapp.generated.resources.settings_continue_watching_section_on_launch
|
||||||
|
import nuvio.composeapp.generated.resources.settings_continue_watching_section_sort_order
|
||||||
import nuvio.composeapp.generated.resources.settings_continue_watching_section_up_next_behavior
|
import nuvio.composeapp.generated.resources.settings_continue_watching_section_up_next_behavior
|
||||||
import nuvio.composeapp.generated.resources.settings_continue_watching_section_visibility
|
import nuvio.composeapp.generated.resources.settings_continue_watching_section_visibility
|
||||||
import nuvio.composeapp.generated.resources.settings_continue_watching_show_description
|
import nuvio.composeapp.generated.resources.settings_continue_watching_show_description
|
||||||
import nuvio.composeapp.generated.resources.settings_continue_watching_show_title
|
import nuvio.composeapp.generated.resources.settings_continue_watching_show_title
|
||||||
|
import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_default
|
||||||
|
import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_default_desc
|
||||||
|
import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_streaming
|
||||||
|
import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_streaming_desc
|
||||||
|
import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_title
|
||||||
import nuvio.composeapp.generated.resources.settings_continue_watching_style_poster
|
import nuvio.composeapp.generated.resources.settings_continue_watching_style_poster
|
||||||
import nuvio.composeapp.generated.resources.settings_continue_watching_style_poster_description
|
import nuvio.composeapp.generated.resources.settings_continue_watching_style_poster_description
|
||||||
import nuvio.composeapp.generated.resources.settings_continue_watching_style_wide
|
import nuvio.composeapp.generated.resources.settings_continue_watching_style_wide
|
||||||
|
|
@ -58,6 +74,7 @@ internal fun LazyListScope.continueWatchingSettingsContent(
|
||||||
showUnairedNextUp: Boolean,
|
showUnairedNextUp: Boolean,
|
||||||
blurNextUp: Boolean,
|
blurNextUp: Boolean,
|
||||||
showResumePromptOnLaunch: Boolean,
|
showResumePromptOnLaunch: Boolean,
|
||||||
|
sortMode: ContinueWatchingSortMode,
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
|
|
@ -145,6 +162,39 @@ internal fun LazyListScope.continueWatchingSettingsContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
item {
|
||||||
|
var showSortModeSheet by remember { mutableStateOf(false) }
|
||||||
|
SettingsSection(
|
||||||
|
title = stringResource(Res.string.settings_continue_watching_section_sort_order),
|
||||||
|
isTablet = isTablet,
|
||||||
|
) {
|
||||||
|
SettingsGroup(isTablet = isTablet) {
|
||||||
|
val currentModeLabel = stringResource(
|
||||||
|
when (sortMode) {
|
||||||
|
ContinueWatchingSortMode.DEFAULT -> Res.string.settings_continue_watching_sort_mode_default
|
||||||
|
ContinueWatchingSortMode.STREAMING_STYLE -> Res.string.settings_continue_watching_sort_mode_streaming
|
||||||
|
}
|
||||||
|
)
|
||||||
|
SettingsNavigationRow(
|
||||||
|
title = stringResource(Res.string.settings_continue_watching_sort_mode_title),
|
||||||
|
description = currentModeLabel,
|
||||||
|
isTablet = isTablet,
|
||||||
|
onClick = { showSortModeSheet = true },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showSortModeSheet) {
|
||||||
|
ContinueWatchingSortModeDialog(
|
||||||
|
currentMode = sortMode,
|
||||||
|
onModeSelected = { mode ->
|
||||||
|
ContinueWatchingPreferencesRepository.setSortMode(mode)
|
||||||
|
showSortModeSheet = false
|
||||||
|
},
|
||||||
|
onDismiss = { showSortModeSheet = false },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -250,3 +300,101 @@ private val ContinueWatchingSectionStyle.descriptionRes: StringResource
|
||||||
ContinueWatchingSectionStyle.Wide -> Res.string.settings_continue_watching_style_wide_description
|
ContinueWatchingSectionStyle.Wide -> Res.string.settings_continue_watching_style_wide_description
|
||||||
ContinueWatchingSectionStyle.Poster -> Res.string.settings_continue_watching_style_poster_description
|
ContinueWatchingSectionStyle.Poster -> Res.string.settings_continue_watching_style_poster_description
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun ContinueWatchingSortModeDialog(
|
||||||
|
currentMode: ContinueWatchingSortMode,
|
||||||
|
onModeSelected: (ContinueWatchingSortMode) -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
val options = listOf(
|
||||||
|
Triple(
|
||||||
|
ContinueWatchingSortMode.DEFAULT,
|
||||||
|
Res.string.settings_continue_watching_sort_mode_default,
|
||||||
|
Res.string.settings_continue_watching_sort_mode_default_desc,
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
ContinueWatchingSortMode.STREAMING_STYLE,
|
||||||
|
Res.string.settings_continue_watching_sort_mode_streaming,
|
||||||
|
Res.string.settings_continue_watching_sort_mode_streaming_desc,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
BasicAlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surface,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(20.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.settings_continue_watching_sort_mode_title),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
options.forEach { (mode, titleRes, descriptionRes) ->
|
||||||
|
val isSelected = mode == currentMode
|
||||||
|
val containerColor = if (isSelected) {
|
||||||
|
MaterialTheme.colorScheme.primary.copy(alpha = 0.14f)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f)
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onModeSelected(mode) },
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
color = containerColor,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 14.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(titleRes),
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(descriptionRes),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
if (isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,7 @@ fun ContinueWatchingSettingsScreen(
|
||||||
showUnairedNextUp = continueWatchingPreferencesUiState.showUnairedNextUp,
|
showUnairedNextUp = continueWatchingPreferencesUiState.showUnairedNextUp,
|
||||||
blurNextUp = continueWatchingPreferencesUiState.blurNextUp,
|
blurNextUp = continueWatchingPreferencesUiState.blurNextUp,
|
||||||
showResumePromptOnLaunch = continueWatchingPreferencesUiState.showResumePromptOnLaunch,
|
showResumePromptOnLaunch = continueWatchingPreferencesUiState.showResumePromptOnLaunch,
|
||||||
|
sortMode = continueWatchingPreferencesUiState.sortMode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -509,6 +509,7 @@ private fun MobileSettingsScreen(
|
||||||
showUnairedNextUp = continueWatchingPreferencesUiState.showUnairedNextUp,
|
showUnairedNextUp = continueWatchingPreferencesUiState.showUnairedNextUp,
|
||||||
blurNextUp = continueWatchingPreferencesUiState.blurNextUp,
|
blurNextUp = continueWatchingPreferencesUiState.blurNextUp,
|
||||||
showResumePromptOnLaunch = continueWatchingPreferencesUiState.showResumePromptOnLaunch,
|
showResumePromptOnLaunch = continueWatchingPreferencesUiState.showResumePromptOnLaunch,
|
||||||
|
sortMode = continueWatchingPreferencesUiState.sortMode,
|
||||||
)
|
)
|
||||||
SettingsPage.PosterCustomization -> posterCustomizationSettingsContent(
|
SettingsPage.PosterCustomization -> posterCustomizationSettingsContent(
|
||||||
isTablet = false,
|
isTablet = false,
|
||||||
|
|
@ -866,6 +867,7 @@ private fun TabletSettingsScreen(
|
||||||
showUnairedNextUp = continueWatchingPreferencesUiState.showUnairedNextUp,
|
showUnairedNextUp = continueWatchingPreferencesUiState.showUnairedNextUp,
|
||||||
blurNextUp = continueWatchingPreferencesUiState.blurNextUp,
|
blurNextUp = continueWatchingPreferencesUiState.blurNextUp,
|
||||||
showResumePromptOnLaunch = continueWatchingPreferencesUiState.showResumePromptOnLaunch,
|
showResumePromptOnLaunch = continueWatchingPreferencesUiState.showResumePromptOnLaunch,
|
||||||
|
sortMode = continueWatchingPreferencesUiState.sortMode,
|
||||||
)
|
)
|
||||||
SettingsPage.PosterCustomization -> posterCustomizationSettingsContent(
|
SettingsPage.PosterCustomization -> posterCustomizationSettingsContent(
|
||||||
isTablet = true,
|
isTablet = true,
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@ private const val COMMENTS_SORT = "likes"
|
||||||
private const val COMMENTS_LIMIT = 100
|
private const val COMMENTS_LIMIT = 100
|
||||||
private const val COMMENTS_CACHE_TTL_MS = 10 * 60_000L
|
private const val COMMENTS_CACHE_TTL_MS = 10 * 60_000L
|
||||||
private val INLINE_SPOILER_REGEX = Regex(
|
private val INLINE_SPOILER_REGEX = Regex(
|
||||||
"\\[spoiler\\].*?\\[/spoiler\\]",
|
"(?is)\\[spoiler\\].*?\\[/spoiler\\]"
|
||||||
setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL),
|
|
||||||
)
|
)
|
||||||
private val INLINE_SPOILER_TAG_REGEX = Regex("\\[/?spoiler\\]", RegexOption.IGNORE_CASE)
|
private val INLINE_SPOILER_TAG_REGEX = Regex("\\[/?spoiler\\]", RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ private data class StoredContinueWatchingPreferences(
|
||||||
val blurNextUp: Boolean = false,
|
val blurNextUp: Boolean = false,
|
||||||
val dismissedNextUpKeys: Set<String> = emptySet(),
|
val dismissedNextUpKeys: Set<String> = emptySet(),
|
||||||
val showResumePromptOnLaunch: Boolean = true,
|
val showResumePromptOnLaunch: Boolean = true,
|
||||||
|
@SerialName("sort_mode")
|
||||||
|
val sortMode: ContinueWatchingSortMode = ContinueWatchingSortMode.DEFAULT,
|
||||||
)
|
)
|
||||||
|
|
||||||
object ContinueWatchingPreferencesRepository {
|
object ContinueWatchingPreferencesRepository {
|
||||||
|
|
@ -97,6 +99,7 @@ object ContinueWatchingPreferencesRepository {
|
||||||
blurNextUp = stored.blurNextUp,
|
blurNextUp = stored.blurNextUp,
|
||||||
dismissedNextUpKeys = stored.dismissedNextUpKeys,
|
dismissedNextUpKeys = stored.dismissedNextUpKeys,
|
||||||
showResumePromptOnLaunch = stored.showResumePromptOnLaunch,
|
showResumePromptOnLaunch = stored.showResumePromptOnLaunch,
|
||||||
|
sortMode = stored.sortMode,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ContinueWatchingPreferencesUiState()
|
ContinueWatchingPreferencesUiState()
|
||||||
|
|
@ -155,6 +158,13 @@ object ContinueWatchingPreferencesRepository {
|
||||||
persist()
|
persist()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setSortMode(mode: ContinueWatchingSortMode) {
|
||||||
|
ensureLoaded()
|
||||||
|
if (_uiState.value.sortMode == mode) return
|
||||||
|
_uiState.value = _uiState.value.copy(sortMode = mode)
|
||||||
|
persist()
|
||||||
|
}
|
||||||
|
|
||||||
fun removeDismissedNextUpKeysForContent(contentId: String) {
|
fun removeDismissedNextUpKeysForContent(contentId: String) {
|
||||||
ensureLoaded()
|
ensureLoaded()
|
||||||
val normalizedContentId = contentId.trim()
|
val normalizedContentId = contentId.trim()
|
||||||
|
|
@ -178,6 +188,7 @@ object ContinueWatchingPreferencesRepository {
|
||||||
blurNextUp = _uiState.value.blurNextUp,
|
blurNextUp = _uiState.value.blurNextUp,
|
||||||
dismissedNextUpKeys = _uiState.value.dismissedNextUpKeys,
|
dismissedNextUpKeys = _uiState.value.dismissedNextUpKeys,
|
||||||
showResumePromptOnLaunch = _uiState.value.showResumePromptOnLaunch,
|
showResumePromptOnLaunch = _uiState.value.showResumePromptOnLaunch,
|
||||||
|
sortMode = _uiState.value.sortMode,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,12 @@ enum class ContinueWatchingSectionStyle {
|
||||||
Poster,
|
Poster,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class ContinueWatchingSortMode {
|
||||||
|
DEFAULT,
|
||||||
|
STREAMING_STYLE,
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class WatchProgressEntry(
|
data class WatchProgressEntry(
|
||||||
val contentType: String,
|
val contentType: String,
|
||||||
|
|
@ -175,6 +181,7 @@ data class ContinueWatchingPreferencesUiState(
|
||||||
val blurNextUp: Boolean = false,
|
val blurNextUp: Boolean = false,
|
||||||
val dismissedNextUpKeys: Set<String> = emptySet(),
|
val dismissedNextUpKeys: Set<String> = emptySet(),
|
||||||
val showResumePromptOnLaunch: Boolean = true,
|
val showResumePromptOnLaunch: Boolean = true,
|
||||||
|
val sortMode: ContinueWatchingSortMode = ContinueWatchingSortMode.DEFAULT,
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun nextUpDismissKey(
|
internal fun nextUpDismissKey(
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ compose-uiToolingPreview = { module = "org.jetbrains.compose.ui:ui-tooling-previ
|
||||||
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
|
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
|
||||||
coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" }
|
coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" }
|
||||||
coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
|
coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
|
||||||
|
coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil" }
|
||||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
|
||||||
ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
|
ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
|
||||||
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
|
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue