From 628771211da39d8e0159d8ad78a61a8ac2a73b9f Mon Sep 17 00:00:00 2001 From: Damontecres Date: Mon, 6 Oct 2025 12:34:27 -0400 Subject: [PATCH] Preferences update, play theme song volume --- .../dolphin/preferences/AppPreference.kt | 37 ++++-- .../dolphin/ui/detail/SeriesDetails.kt | 2 +- .../dolphin/ui/detail/SeriesViewModel.kt | 22 +++- .../ui/detail/series/SeriesOverview.kt | 1 + .../ui/preferences/PreferencesContent.kt | 46 +++---- .../damontecres/dolphin/util/Version.kt | 112 ++++++++++++++++++ app/src/main/proto/DolphinDataStore.proto | 11 +- app/src/main/res/values/preferences.xml | 12 ++ 8 files changed, 206 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/com/github/damontecres/dolphin/util/Version.kt create mode 100644 app/src/main/res/values/preferences.xml diff --git a/app/src/main/java/com/github/damontecres/dolphin/preferences/AppPreference.kt b/app/src/main/java/com/github/damontecres/dolphin/preferences/AppPreference.kt index 14efc50b..ba065c6d 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/preferences/AppPreference.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/preferences/AppPreference.kt @@ -176,15 +176,16 @@ sealed interface AppPreference { ) val PlayThemeMusic = - AppSwitchPreference( + AppChoicePreference( title = R.string.play_theme_music, - defaultValue = true, + defaultValue = ThemeSongVolume.MEDIUM, getter = { it.interfacePreferences.playThemeSongs }, setter = { prefs, value -> prefs.updateInterfacePreferences { playThemeSongs = value } }, - summaryOn = R.string.enabled, - summaryOff = R.string.disabled, + displayValues = R.array.theme_song_volume, + indexToValue = { ThemeSongVolume.forNumber(it) }, + valueToIndex = { it.number }, ) val PlaybackDebugInfo = @@ -199,6 +200,12 @@ sealed interface AppPreference { summaryOff = R.string.hide, ) + val InstalledVersion = + AppClickablePreference( + title = R.string.installed_version, + getter = { }, + setter = { prefs, _ -> prefs }, + ) // val AutoCheckForUpdates = // AppSwitchPreference( // title = R.string.check_for_updates, @@ -237,17 +244,29 @@ val basicPreferences = title = R.string.basic_interface, preferences = listOf( - AppPreference.SkipForward, - AppPreference.SkipBack, - AppPreference.ControllerTimeout, - AppPreference.SeekBarSteps, AppPreference.HomePageItems, AppPreference.RewatchNextUp, AppPreference.PlayThemeMusic, - AppPreference.OssLicenseInfo, + ), + ), + PreferenceGroup( + title = R.string.playback, + preferences = + listOf( + AppPreference.SkipForward, + AppPreference.SkipBack, + AppPreference.ControllerTimeout, AppPreference.PlaybackDebugInfo, ), ), + PreferenceGroup( + title = R.string.about, + preferences = + listOf( + AppPreference.InstalledVersion, + AppPreference.OssLicenseInfo, + ), + ), ) val uiPreferences = listOf() diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/detail/SeriesDetails.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/detail/SeriesDetails.kt index 2fbf8cc1..3305327a 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/detail/SeriesDetails.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/detail/SeriesDetails.kt @@ -66,7 +66,7 @@ fun SeriesDetails( viewModel: SeriesViewModel = hiltViewModel(), ) { LaunchedEffect(Unit) { - viewModel.init(destination.itemId, destination.item, null, null) + viewModel.init(preferences, destination.itemId, destination.item, null, null) } val loading by viewModel.loading.observeAsState(LoadingState.Loading) diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/detail/SeriesViewModel.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/detail/SeriesViewModel.kt index 6b7bdba3..95d52782 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/detail/SeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/detail/SeriesViewModel.kt @@ -14,6 +14,8 @@ import com.github.damontecres.dolphin.data.model.BaseItem import com.github.damontecres.dolphin.data.model.Person import com.github.damontecres.dolphin.data.model.Video import com.github.damontecres.dolphin.hilt.AuthOkHttpClient +import com.github.damontecres.dolphin.preferences.ThemeSongVolume +import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.ui.letNotEmpty import com.github.damontecres.dolphin.util.ApiRequestPager import com.github.damontecres.dolphin.util.ExceptionHandler @@ -59,6 +61,7 @@ class SeriesViewModel val people = MutableLiveData>(listOf()) fun init( + prefs: UserPreferences, itemId: UUID, potential: BaseItem?, season: Int?, @@ -89,7 +92,7 @@ class SeriesViewModel people.map { Person.fromDto(it, api) } }.orEmpty() } - maybePlayThemeSong() + maybePlayThemeSong(prefs.appPreferences.interfacePreferences.playThemeSongs) } else { withContext(Dispatchers.Main) { seasons.value = ItemListAndMapping.empty() @@ -101,8 +104,19 @@ class SeriesViewModel } @OptIn(UnstableApi::class) - private fun maybePlayThemeSong() { - // TODO user preference to enable/disable this + private fun maybePlayThemeSong(playThemeSongs: ThemeSongVolume) { + val volume = + when (playThemeSongs) { + ThemeSongVolume.UNRECOGNIZED, + ThemeSongVolume.DISABLED, + -> return + + ThemeSongVolume.LOWEST -> .1f + ThemeSongVolume.LOW -> .25f + ThemeSongVolume.MEDIUM -> .5f + ThemeSongVolume.HIGH -> .75f + ThemeSongVolume.HIGHEST -> 1f + } viewModelScope.launch(ExceptionHandler()) { val themeSongs = api.libraryApi.getThemeSongs(seriesId).content themeSongs.items.firstOrNull()?.let { theme -> @@ -123,7 +137,7 @@ class SeriesViewModel ), ).build() .apply { - volume = .1f + this.volume = volume playWhenReady = true this@SeriesViewModel.player = this } diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/detail/series/SeriesOverview.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/detail/series/SeriesOverview.kt index 0003c308..47261e84 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/detail/series/SeriesOverview.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/detail/series/SeriesOverview.kt @@ -57,6 +57,7 @@ fun SeriesOverview( OneTimeLaunchedEffect { Timber.v("SeriesDetailParent: itemId=${destination.itemId}, initialSeasonEpisode=$initialSeasonEpisode") viewModel.init( + preferences, destination.itemId, destination.item, initialSeasonEpisode?.season, diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/preferences/PreferencesContent.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/preferences/PreferencesContent.kt index 61b5a4cb..e0870047 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/preferences/PreferencesContent.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/preferences/PreferencesContent.kt @@ -20,6 +20,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -76,7 +77,8 @@ fun PreferencesContent( } val movementSounds = true - val installedVersion = remember { "v1.0.0" } + val installedVersion = + remember { context.packageManager.getPackageInfo(context.packageName, 0).versionName } var updateVersion by remember { mutableStateOf(null) } val updateAvailable = false // remember(updateVersion) { updateVersion?.version?.isGreaterThan(installedVersion) == true } @@ -188,27 +190,27 @@ fun PreferencesContent( } } when (pref) { -// AppPreference.InstalledVersion -> { -// var clickCount by remember { mutableIntStateOf(0) } -// ClickPreference( -// title = stringResource(R.string.installed_version), -// onClick = { -// if (movementSounds) playOnClickSound(context) -// if (clickCount++ >= 2) { -// clickCount = 0 -// // navigationManager.navigateTo(Destination.Debug) -// } -// }, -// summary = installedVersion.toString(), -// interactionSource = interactionSource, -// modifier = -// Modifier -// .ifElse( -// groupIndex == focusedIndex.first && prefIndex == focusedIndex.second, -// Modifier.focusRequester(focusRequester), -// ), -// ) -// } + AppPreference.InstalledVersion -> { + var clickCount by remember { mutableIntStateOf(0) } + ClickPreference( + title = stringResource(R.string.installed_version), + onClick = { + if (movementSounds) playOnClickSound(context) + if (clickCount++ >= 2) { + clickCount = 0 + // navigationManager.navigateTo(Destination.Debug) + } + }, + summary = installedVersion.toString(), + interactionSource = interactionSource, + modifier = + Modifier + .ifElse( + groupIndex == focusedIndex.first && prefIndex == focusedIndex.second, + Modifier.focusRequester(focusRequester), + ), + ) + } // AppPreference.Update -> { // ClickPreference( diff --git a/app/src/main/java/com/github/damontecres/dolphin/util/Version.kt b/app/src/main/java/com/github/damontecres/dolphin/util/Version.kt new file mode 100644 index 00000000..bf9e56b5 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/util/Version.kt @@ -0,0 +1,112 @@ +package com.github.damontecres.dolphin.util + +import kotlinx.serialization.Serializable + +@Serializable +data class Version( + val major: Int, + val minor: Int, + val patch: Int, + val numCommits: Int? = null, + val hash: String? = null, +) { + /** + * Is this version at least the given version + */ + fun isAtLeast(version: Version): Boolean { + if (this.major > version.major) { + return true + } else if (this.major == version.major) { + if (this.minor > version.minor) { + return true + } else if (this.minor == version.minor) { + if (this.patch > version.patch) { + return true + } else if (this.patch == version.patch) { + if (this.compareNumCommits(version) >= 0) { + return true + } + } + } + } + return false + } + + /** + * Is this greater than the given version (and not equal to!) + */ + fun isGreaterThan(version: Version): Boolean { + if (this.major > version.major) { + return true + } else if (this.major == version.major) { + if (this.minor > version.minor) { + return true + } else if (this.minor == version.minor) { + if (this.patch > version.patch) { + return true + } else if (this.patch == version.patch) { + if (this.compareNumCommits(version) > 0) { + return true + } + } + } + } + return false + } + + /** + * Is this less than the given version (and not equal to!) + */ + fun isLessThan(version: Version): Boolean = this != version && isEqualOrBefore(version) + + /** + * Is this equal to or before the specified version + */ + fun isEqualOrBefore(version: Version): Boolean = !isGreaterThan(version) + + private fun compareNumCommits(version: Version): Int = (this.numCommits ?: 0) - (version.numCommits ?: 0) + + override fun toString(): String = + if (numCommits != null && hash != null) { + "v$major.$minor.$patch-$numCommits-g$hash" + } else { + "v$major.$minor.$patch" + } + + companion object { + private val VERSION_REGEX = Regex("v?(\\d+)\\.(\\d+)\\.(\\d+)(-(\\d+)-g([a-zA-Z0-9]+))?") + + /** + * Parse a version string throwing if it is invalid + */ + fun fromString(version: String): Version { + val v = tryFromString(version) + if (v == null) { + throw IllegalArgumentException(version) + } else { + return v + } + } + + /** + * Attempt to parse a version string or else return null + */ + fun tryFromString(version: String?): Version? { + if (version == null) { + return null + } + val m = VERSION_REGEX.matchEntire(version) + return if (m == null) { + null + } else { + val major = m.groups[1]!!.value.toInt() + val minor = m.groups[2]!!.value.toInt() + val patch = m.groups[3]!!.value.toInt() + // group 4 is the optional commit info + val numCommits = m.groups[5]?.value?.toInt() + val hash = m.groups[6]?.value + Version(major, minor, patch, numCommits, hash) + } + } + } +} diff --git a/app/src/main/proto/DolphinDataStore.proto b/app/src/main/proto/DolphinDataStore.proto index dd5d8842..854ad834 100644 --- a/app/src/main/proto/DolphinDataStore.proto +++ b/app/src/main/proto/DolphinDataStore.proto @@ -16,8 +16,17 @@ message HomePagePreferences{ bool enable_rewatching_next_up = 2; } +enum ThemeSongVolume { + DISABLED = 0; + LOWEST = 1; + LOW = 2; + MEDIUM = 3; + HIGH = 4; + HIGHEST = 5; +} + message InterfacePreferences { - bool play_theme_songs = 1; + ThemeSongVolume play_theme_songs = 1; } message AppPreferences { diff --git a/app/src/main/res/values/preferences.xml b/app/src/main/res/values/preferences.xml new file mode 100644 index 00000000..b8a83b34 --- /dev/null +++ b/app/src/main/res/values/preferences.xml @@ -0,0 +1,12 @@ + + + + Disabled + Lowest + Low + Medium + High + Full + + +