Skip to content
This repository has been archived by the owner on Nov 12, 2024. It is now read-only.

Commit

Permalink
Add ignore specials preference (#1488)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbanes authored Aug 28, 2023
1 parent 834401b commit 8fbb022
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ val EnTiviStrings = TiviStrings(
settingsDataSaverTitle = "Data saver",
settingsDynamicColorSummary = "Use colors derived from your wallpaper",
settingsDynamicColorTitle = "Dynamic colors",
settingsIgnoreSpecialsTitle = "Ignore specials",
settingsIgnoreSpecialsSummary = "Automatically ignore specials",
settingsOpenSource = "Open source licenses",
settingsOpenSourceSummary = "Tivi 💞open source",
settingsThemeTitle = "Theme",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ data class TiviStrings(
val settingsDataSaverTitle: String,
val settingsDynamicColorSummary: String,
val settingsDynamicColorTitle: String,
val settingsIgnoreSpecialsTitle: String,
val settingsIgnoreSpecialsSummary: String,
val settingsOpenSource: String,
val settingsOpenSourceSummary: String,
val settingsThemeTitle: String,
Expand Down
42 changes: 0 additions & 42 deletions core/preferences/src/androidMain/res/values/preferences.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ interface TiviPreferences {
var upNextFollowedOnly: Boolean
fun observeUpNextFollowedOnly(): Flow<Boolean>

var ignoreSpecials: Boolean
fun observeIgnoreSpecials(): Flow<Boolean>

enum class Theme {
LIGHT,
DARK,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ class TiviPreferencesImpl(
override fun observeUpNextFollowedOnly(): Flow<Boolean> {
return flowSettings.getBooleanFlow(KEY_UPNEXT_FOLLOWED_ONLY, false)
}

override var ignoreSpecials: Boolean
get() = settings.getBoolean(KEY_IGNORE_SPECIALS, true)
set(value) = settings.putBoolean(KEY_IGNORE_SPECIALS, value)

override fun observeIgnoreSpecials(): Flow<Boolean> {
return flowSettings.getBooleanFlow(KEY_IGNORE_SPECIALS, true)
}
}

private val Theme.storageKey: String
Expand All @@ -90,6 +98,7 @@ internal const val KEY_DATA_SAVER = "pref_data_saver"
internal const val KEY_LIBRARY_FOLLOWED_ACTIVE = "pref_library_followed_active"
internal const val KEY_LIBRARY_WATCHED_ACTIVE = "pref_library_watched_active"
internal const val KEY_UPNEXT_FOLLOWED_ONLY = "pref_upnext_followedonly_active"
internal const val KEY_IGNORE_SPECIALS = "pref_ignore_specials"

internal const val THEME_LIGHT_VALUE = "light"
internal const val THEME_DARK_VALUE = "dark"
Expand Down
1 change: 1 addition & 0 deletions data/episodes/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
api(projects.core.preferences)
api(projects.data.models)
api(projects.data.traktauth)
implementation(projects.data.db)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,43 @@ package app.tivi.domain.observers

import app.tivi.data.compoundmodels.SeasonWithEpisodesAndWatches
import app.tivi.data.episodes.SeasonsEpisodesRepository
import app.tivi.data.models.Season
import app.tivi.domain.SubjectInteractor
import app.tivi.settings.TiviPreferences
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import me.tatarka.inject.annotations.Inject

@Inject
class ObserveShowSeasonsEpisodesWatches(
private val seasonsEpisodesRepository: SeasonsEpisodesRepository,
private val preferences: TiviPreferences,
) : SubjectInteractor<ObserveShowSeasonsEpisodesWatches.Params, List<SeasonWithEpisodesAndWatches>>() {

@OptIn(ExperimentalCoroutinesApi::class)
override fun createObservable(params: Params): Flow<List<SeasonWithEpisodesAndWatches>> {
return seasonsEpisodesRepository.observeSeasonsWithEpisodesWatchedForShow(params.showId)
return preferences.observeIgnoreSpecials().flatMapLatest { ignoreSpecials ->
if (ignoreSpecials) {
seasonsEpisodesRepository
.observeSeasonsWithEpisodesWatchedForShow(params.showId)
.map { seasonsWithEpisodes ->
seasonsWithEpisodes.map { seasonWithEpisodes ->
if (seasonWithEpisodes.season.number == Season.NUMBER_SPECIALS) {
seasonWithEpisodes.copy(
season = seasonWithEpisodes.season.copy(ignored = true),
)
} else {
seasonWithEpisodes
}
}
}
} else {
// If we're not ignoring specials, just use the flow as-is
seasonsEpisodesRepository.observeSeasonsWithEpisodesWatchedForShow(params.showId)
}
}
}

data class Params(val showId: Long)
Expand Down
11 changes: 9 additions & 2 deletions ui/settings/src/commonMain/kotlin/app/tivi/settings/Settings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package app.tivi.settings

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
Expand Down Expand Up @@ -138,6 +137,15 @@ internal fun Settings(
)
}

item {
CheckboxPreference(
title = strings.settingsIgnoreSpecialsTitle,
summaryOff = strings.settingsIgnoreSpecialsSummary,
onCheckClicked = { eventSink(SettingsUiEvent.ToggleIgnoreSpecials) },
checked = state.ignoreSpecials,
)
}

itemSpacer(24.dp)

stickyHeader {
Expand Down Expand Up @@ -249,7 +257,6 @@ private fun CheckboxPreference(
title = title,
summary = {
if (summaryOff != null && summaryOn != null) {
@OptIn(ExperimentalAnimationApi::class)
AnimatedContent(checked) { target ->
Text(text = if (target) summaryOn else summaryOff)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class SettingsPresenter(
val useLessData by remember { preferences.observeUseLessData() }
.collectAsState(false)

val ignoreSpecials by remember { preferences.observeIgnoreSpecials() }
.collectAsState(true)

fun eventSink(event: SettingsUiEvent) {
when (event) {
SettingsUiEvent.NavigateUp -> navigator.pop()
Expand All @@ -61,6 +64,9 @@ class SettingsPresenter(
SettingsUiEvent.ToggleUseLessData -> {
preferences.useLessData = !preferences.useLessData
}
SettingsUiEvent.ToggleIgnoreSpecials -> {
preferences.ignoreSpecials = !preferences.ignoreSpecials
}
SettingsUiEvent.NavigatePrivacyPolicy -> {
navigator.goTo(UrlScreen("https://chrisbanes.github.io/tivi/privacypolicy"))
}
Expand All @@ -72,6 +78,7 @@ class SettingsPresenter(
useDynamicColors = useDynamicColors,
dynamicColorsAvailable = DynamicColorsAvailable,
useLessData = useLessData,
ignoreSpecials = ignoreSpecials,
applicationInfo = applicationInfo,
eventSink = ::eventSink,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ data class SettingsUiState(
val dynamicColorsAvailable: Boolean,
val useDynamicColors: Boolean,
val useLessData: Boolean,
val ignoreSpecials: Boolean,
val applicationInfo: ApplicationInfo,
val eventSink: (SettingsUiEvent) -> Unit,
) : CircuitUiState
Expand All @@ -23,5 +24,6 @@ sealed interface SettingsUiEvent : CircuitUiEvent {
object NavigatePrivacyPolicy : SettingsUiEvent
object ToggleUseDynamicColors : SettingsUiEvent
object ToggleUseLessData : SettingsUiEvent
object ToggleIgnoreSpecials : SettingsUiEvent
data class SetTheme(val theme: TiviPreferences.Theme) : SettingsUiEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Snackbar
Expand All @@ -66,6 +67,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
Expand Down Expand Up @@ -780,73 +782,81 @@ private fun SeasonRow(
modifier: Modifier = Modifier,
nextToAirDate: Instant? = null,
) {
Row(
modifier = modifier
.clip(MaterialTheme.shapes.medium)
.clickable(enabled = !season.ignored) {
openSeason(season.id)
}
.heightIn(min = 48.dp)
.wrapContentHeight(Alignment.CenterVertically)
.padding(contentPadding),
) {
Column(
modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically),
) {
val textCreator = LocalTiviTextCreator.current
val contentColor = when {
season.ignored -> LocalContentColor.current.copy(alpha = 0.4f)
else -> LocalContentColor.current
}

Text(
text = season.title ?: LocalStrings.current.seasonTitleFallback(season.number!!),
style = MaterialTheme.typography.bodyLarge,
)
CompositionLocalProvider(LocalContentColor provides contentColor) {
Row(
modifier = modifier
.clip(MaterialTheme.shapes.medium)
.clickable(enabled = !season.ignored) {
openSeason(season.id)
}
.heightIn(min = 48.dp)
.wrapContentHeight(Alignment.CenterVertically)
.padding(contentPadding),
) {
Column(
modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically),
) {
val textCreator = LocalTiviTextCreator.current

Spacer(Modifier.height(4.dp))
Text(
text = season.title
?: LocalStrings.current.seasonTitleFallback(season.number!!),
style = MaterialTheme.typography.bodyLarge,
)

Text(
text = textCreator.seasonSummaryText(
watched = episodesWatched,
toWatch = episodesToWatch,
toAir = episodesToAir,
nextToAirDate = nextToAirDate,
).toString(),
style = MaterialTheme.typography.bodySmall,
)
Spacer(Modifier.height(4.dp))

if (!season.ignored && episodesAired > 0) {
LinearProgressIndicator(
progress = episodesWatched / episodesAired.toFloat(),
modifier = Modifier
.padding(top = 4.dp)
.fillMaxWidth(),
Text(
text = textCreator.seasonSummaryText(
watched = episodesWatched,
toWatch = episodesToWatch,
toAir = episodesToAir,
nextToAirDate = nextToAirDate,
).toString(),
style = MaterialTheme.typography.bodySmall,
)

if (!season.ignored && episodesAired > 0) {
LinearProgressIndicator(
progress = episodesWatched / episodesAired.toFloat(),
modifier = Modifier
.padding(top = 4.dp)
.fillMaxWidth(),
)
}
}
}

Box(modifier = Modifier.align(Alignment.CenterVertically)) {
var showMenu by remember { mutableStateOf(false) }
Box(modifier = Modifier.align(Alignment.CenterVertically)) {
var showMenu by remember { mutableStateOf(false) }

IconButton(onClick = { showMenu = true }) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = LocalStrings.current.cdOpenOverflow,
IconButton(onClick = { showMenu = true }) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = LocalStrings.current.cdOpenOverflow,
)
}

SeasonDropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
season = season,
episodesAired = episodesAired,
episodesWatched = episodesWatched,
episodesToAir = episodesToAir,
onSeasonFollowed = onSeasonFollowed,
onSeasonUnfollowed = onSeasonUnfollowed,
unfollowPreviousSeasons = unfollowPreviousSeasons,
onMarkSeasonWatched = onMarkSeasonWatched,
onMarkSeasonUnwatched = onMarkSeasonUnwatched,
)
}

SeasonDropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
season = season,
episodesAired = episodesAired,
episodesWatched = episodesWatched,
episodesToAir = episodesToAir,
onSeasonFollowed = onSeasonFollowed,
onSeasonUnfollowed = onSeasonUnfollowed,
unfollowPreviousSeasons = unfollowPreviousSeasons,
onMarkSeasonWatched = onMarkSeasonWatched,
onMarkSeasonUnwatched = onMarkSeasonUnwatched,
)
}
}
}
Expand Down

0 comments on commit 8fbb022

Please sign in to comment.