From 95216143dc75541010720179c96be4ffac865967 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Sat, 26 Sep 2026 17:32:01 +0000 Subject: [PATCH] polish stream quality settings - label the rows 'Stream quality: Wi-Fi' / 'Stream quality: Mobile' - default mobile to 192kbps/mp3 while Wi-Fi stays unlimited/server default - apply the selection only when Done is pressed, cancelling discards the draft - fade the bottom of the dialog list so the remaining options are visible as scrollable --- .../subtracks/data/prefs/UserPreferences.kt | 22 ++++-- .../subtracks/ui/settings/SettingsScreen.kt | 72 ++++++++++++------- .../data/prefs/UserPreferencesTest.kt | 2 +- 3 files changed, 65 insertions(+), 31 deletions(-) diff --git a/app/src/main/kotlin/com/subtracks/data/prefs/UserPreferences.kt b/app/src/main/kotlin/com/subtracks/data/prefs/UserPreferences.kt index ec00b76b..0c612de9 100644 --- a/app/src/main/kotlin/com/subtracks/data/prefs/UserPreferences.kt +++ b/app/src/main/kotlin/com/subtracks/data/prefs/UserPreferences.kt @@ -56,7 +56,9 @@ class UserPreferences( fun streamQuality(mode: NetworkMode): Flow = store.data.map { prefs -> - prefs[streamQualityKey(mode)]?.let(::decodeStreamQuality) ?: legacyStreamQuality(prefs) + prefs[streamQualityKey(mode)]?.let(::decodeStreamQuality) + ?: legacyStreamQuality(prefs) + ?: defaultStreamQuality(mode) } suspend fun setStreamQuality( @@ -82,11 +84,21 @@ class UserPreferences( ) } - private fun legacyStreamQuality(prefs: Preferences): StreamQuality = - StreamQuality( - maxBitrate = prefs[LEGACY_BITRATE] ?: 0, - format = prefs[LEGACY_FORMAT]?.takeIf { it.isNotEmpty() }, + private fun legacyStreamQuality(prefs: Preferences): StreamQuality? { + val bitrate = prefs[LEGACY_BITRATE] + val format = prefs[LEGACY_FORMAT] + if (bitrate == null && format == null) return null + return StreamQuality( + maxBitrate = bitrate ?: 0, + format = format?.takeIf { it.isNotEmpty() }, ) + } + + private fun defaultStreamQuality(mode: NetworkMode): StreamQuality = + when (mode) { + NetworkMode.Wifi -> StreamQuality() + NetworkMode.Mobile -> StreamQuality(maxBitrate = 192, format = "mp3") + } private fun decode( stored: String?, diff --git a/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt index 02350eaa..521e3d79 100644 --- a/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt @@ -1,7 +1,9 @@ package com.subtracks.ui.settings +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -39,6 +41,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -150,7 +153,7 @@ fun SettingsScreen( item { SectionHeader("Network") } item { ListItem( - headlineContent = { Text("Wi-Fi") }, + headlineContent = { Text("Stream quality: Wi-Fi") }, supportingContent = { Text(qualityLabel(wifiQuality)) }, modifier = Modifier.clickable { dialog = SettingsDialog.WifiQuality }, colors = ListItemDefaults.colors(containerColor = Color.Transparent), @@ -158,7 +161,7 @@ fun SettingsScreen( } item { ListItem( - headlineContent = { Text("Mobile data") }, + headlineContent = { Text("Stream quality: Mobile") }, supportingContent = { Text(qualityLabel(mobileQuality)) }, modifier = Modifier.clickable { dialog = SettingsDialog.MobileQuality }, colors = ListItemDefaults.colors(containerColor = Color.Transparent), @@ -171,7 +174,7 @@ fun SettingsScreen( when (dialog) { SettingsDialog.WifiQuality -> { QualityDialog( - title = "Wi-Fi", + title = "Stream quality: Wi-Fi", quality = wifiQuality, onSelect = onWifiQualityChange, onDismiss = { dialog = null }, @@ -180,7 +183,7 @@ fun SettingsScreen( SettingsDialog.MobileQuality -> { QualityDialog( - title = "Mobile data", + title = "Stream quality: Mobile", quality = mobileQuality, onSelect = onMobileQualityChange, onDismiss = { dialog = null }, @@ -205,34 +208,53 @@ private fun QualityDialog( onDismiss: () -> Unit, ) { var draft by remember(quality) { mutableStateOf(quality) } + val scrollState = rememberScrollState() AlertDialog( onDismissRequest = onDismiss, title = { Text(title) }, text = { - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - ChoiceGroup( - header = "Maximum bitrate", - options = bitrateOptions.map { it to bitrateLabel(it) }, - selected = draft.maxBitrate, - onSelect = { - draft = draft.copy(maxBitrate = it) - onSelect(draft) - }, - ) - Spacer(Modifier.height(16.dp)) - ChoiceGroup( - header = "Preferred format", - options = streamFormats.map { it to (it ?: "Use server default") }, - selected = draft.format, - onSelect = { - draft = draft.copy(format = it) - onSelect(draft) - }, - ) + Box { + Column(modifier = Modifier.verticalScroll(scrollState)) { + ChoiceGroup( + header = "Maximum bitrate", + options = bitrateOptions.map { it to bitrateLabel(it) }, + selected = draft.maxBitrate, + onSelect = { draft = draft.copy(maxBitrate = it) }, + ) + Spacer(Modifier.height(16.dp)) + ChoiceGroup( + header = "Preferred format", + options = streamFormats.map { it to (it ?: "Use server default") }, + selected = draft.format, + onSelect = { draft = draft.copy(format = it) }, + ) + } + if (scrollState.canScrollForward) { + Box( + modifier = + Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .height(32.dp) + .background( + Brush.verticalGradient( + listOf(Color.Transparent, MaterialTheme.colorScheme.surfaceContainerHigh), + ), + ), + ) + } } }, confirmButton = { - TextButton(onClick = onDismiss) { Text("Done") } + TextButton( + onClick = { + onSelect(draft) + onDismiss() + }, + ) { Text("Done") } + }, + dismissButton = { + TextButton(onClick = onDismiss) { Text("Cancel") } }, ) } diff --git a/app/src/test/kotlin/com/subtracks/data/prefs/UserPreferencesTest.kt b/app/src/test/kotlin/com/subtracks/data/prefs/UserPreferencesTest.kt index dbba72c6..41715e31 100644 --- a/app/src/test/kotlin/com/subtracks/data/prefs/UserPreferencesTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/prefs/UserPreferencesTest.kt @@ -50,7 +50,7 @@ class UserPreferencesTest { val prefs = UserPreferences(store) assertEquals(StreamQuality(), prefs.streamQuality(NetworkMode.Wifi).first()) - assertEquals(StreamQuality(), prefs.streamQuality(NetworkMode.Mobile).first()) + assertEquals(StreamQuality(192, "mp3"), prefs.streamQuality(NetworkMode.Mobile).first()) prefs.setStreamQuality(NetworkMode.Wifi, StreamQuality(320, null)) prefs.setStreamQuality(NetworkMode.Mobile, StreamQuality(96, "opus"))