mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-22 17:41:39 +00:00
feat(settings): add SettingsScreen and integrate settings management
This commit is contained in:
@@ -9,6 +9,7 @@ import hu.bbara.purefin.ui.screen.AppScreen
|
||||
import hu.bbara.purefin.ui.screen.episode.EpisodeScreen
|
||||
import hu.bbara.purefin.ui.screen.home.components.search.SearchScreen
|
||||
import hu.bbara.purefin.ui.screen.movie.MovieScreen
|
||||
import hu.bbara.purefin.ui.screen.settings.SettingsScreen
|
||||
import hu.bbara.purefin.ui.screen.series.SeriesScreen
|
||||
|
||||
fun EntryProviderScope<Route>.appRouteEntryBuilder() {
|
||||
@@ -53,4 +54,7 @@ fun EntryProviderScope<Route>.appRouteEntryBuilder() {
|
||||
entry<Route.LoginRoute> {
|
||||
LoginScreen()
|
||||
}
|
||||
entry<Route.SettingsRoute> {
|
||||
SettingsScreen()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ fun AppScreen(
|
||||
onProfileClick = {},
|
||||
onCheckForUpdates = updateViewModel::checkForUpdates,
|
||||
isCheckingForUpdates = isCheckingForUpdates,
|
||||
onSettingsClick = {},
|
||||
onSettingsClick = viewModel::openSettings,
|
||||
onLogoutClick = viewModel::logout,
|
||||
onSearchClick = viewModel::openSearch,
|
||||
snackbarHostState = snackbarHostState,
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package hu.bbara.purefin.ui.screen.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import hu.bbara.purefin.feature.settings.SettingsViewModel
|
||||
import hu.bbara.purefin.settings.SettingsOptions
|
||||
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
|
||||
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBarIconButton
|
||||
import hu.bbara.purefin.ui.screen.settings.components.BooleanSettingItem
|
||||
import hu.bbara.purefin.ui.screen.settings.components.NumberSettingItem
|
||||
import hu.bbara.purefin.ui.screen.settings.components.StringSettingItem
|
||||
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
viewModel: SettingsViewModel = hiltViewModel(),
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||
topBar = {
|
||||
SettingsTopBar(onBack = viewModel::onBack)
|
||||
}
|
||||
) { innerPadding ->
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = "Settings",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Text(
|
||||
text = "Playback",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
SettingsOptions.numberSettings.forEach { option ->
|
||||
item(key = option.key) {
|
||||
val value by viewModel.value(option).collectAsState(initial = option.defaultValue)
|
||||
NumberSettingItem(
|
||||
title = option.title,
|
||||
value = value,
|
||||
valueRange = option.valueRange,
|
||||
onValueChange = { viewModel.set(option, it) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
|
||||
SettingsOptions.booleanSettings.forEach { option ->
|
||||
item(key = option.key) {
|
||||
val value by viewModel.value(option).collectAsState(initial = option.defaultValue)
|
||||
BooleanSettingItem(
|
||||
title = option.title,
|
||||
value = value,
|
||||
onValueChange = { viewModel.set(option, it) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
|
||||
SettingsOptions.stringSettings.forEach { option ->
|
||||
item(key = option.key) {
|
||||
val value by viewModel.value(option).collectAsState(initial = option.defaultValue)
|
||||
StringSettingItem(
|
||||
title = option.title,
|
||||
value = value,
|
||||
onValueChange = { viewModel.set(option, it) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsTopBar(
|
||||
onBack: () -> Unit
|
||||
) {
|
||||
DefaultTopBar(
|
||||
leftActions = {
|
||||
DefaultTopBarIconButton(
|
||||
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
onClick = onBack
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package hu.bbara.purefin.ui.screen.settings.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun BooleanSettingItem(
|
||||
title: String,
|
||||
value: Boolean,
|
||||
onValueChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier.Companion
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = value,
|
||||
onCheckedChange = onValueChange
|
||||
)
|
||||
},
|
||||
modifier = modifier.clickable { onValueChange(!value) }
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package hu.bbara.purefin.ui.screen.settings.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Text
|
||||
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.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun NumberSettingItem(
|
||||
title: String,
|
||||
value: Double,
|
||||
valueRange: ClosedFloatingPointRange<Double>,
|
||||
onValueChange: (Double) -> Unit,
|
||||
modifier: Modifier = Modifier.Companion
|
||||
) {
|
||||
var sliderValue by remember(value) { mutableStateOf(value.toFloat()) }
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
Text(
|
||||
text = String.Companion.format(Locale.US, "%.1f", sliderValue),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Slider(
|
||||
value = sliderValue,
|
||||
onValueChange = { sliderValue = it },
|
||||
onValueChangeFinished = { onValueChange(sliderValue.toDouble()) },
|
||||
valueRange = valueRange.start.toFloat()..valueRange.endInclusive.toFloat(),
|
||||
modifier = Modifier.Companion.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package hu.bbara.purefin.ui.screen.settings.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun StringSettingItem(
|
||||
title: String,
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier.Companion
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
label = { Text(title) },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.Companion.Words
|
||||
),
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||
)
|
||||
}
|
||||
@@ -256,6 +256,10 @@ class AppViewModel @Inject constructor(
|
||||
navigationManager.navigate(Route.HomeSearchRoute)
|
||||
}
|
||||
|
||||
fun openSettings() {
|
||||
navigationManager.navigate(Route.SettingsRoute)
|
||||
}
|
||||
|
||||
fun logout() {
|
||||
viewModelScope.launch {
|
||||
userSessionRepository.setLoggedIn(false)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package hu.bbara.purefin.feature.settings
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import hu.bbara.purefin.navigation.NavigationManager
|
||||
import hu.bbara.purefin.settings.BooleanSetting
|
||||
import hu.bbara.purefin.settings.NumberSetting
|
||||
import hu.bbara.purefin.settings.SettingsRepository
|
||||
import hu.bbara.purefin.settings.StringSetting
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class SettingsViewModel @Inject constructor(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val navigationManager: NavigationManager,
|
||||
) : ViewModel() {
|
||||
|
||||
fun value(option: NumberSetting) = settingsRepository.value(option)
|
||||
|
||||
fun value(option: BooleanSetting) = settingsRepository.value(option)
|
||||
|
||||
fun value(option: StringSetting) = settingsRepository.value(option)
|
||||
|
||||
fun set(option: NumberSetting, value: Double) {
|
||||
viewModelScope.launch {
|
||||
settingsRepository.set(option, value)
|
||||
}
|
||||
}
|
||||
|
||||
fun set(option: BooleanSetting, value: Boolean) {
|
||||
viewModelScope.launch {
|
||||
settingsRepository.set(option, value)
|
||||
}
|
||||
}
|
||||
|
||||
fun set(option: StringSetting, value: String) {
|
||||
viewModelScope.launch {
|
||||
settingsRepository.set(option, value)
|
||||
}
|
||||
}
|
||||
|
||||
fun onBack() {
|
||||
navigationManager.pop()
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,9 @@ sealed interface Route : NavKey {
|
||||
@Serializable
|
||||
data object LoginRoute : Route
|
||||
|
||||
@Serializable
|
||||
data object SettingsRoute : Route
|
||||
|
||||
@Serializable
|
||||
data class PlayerRoute(val mediaId: String) : Route
|
||||
}
|
||||
|
||||
@@ -2,20 +2,50 @@ package hu.bbara.purefin.settings
|
||||
|
||||
sealed interface SettingOption<T> {
|
||||
val key: String
|
||||
val title: String
|
||||
val defaultValue: T
|
||||
}
|
||||
|
||||
data class NumberSetting(
|
||||
override val key: String,
|
||||
override val defaultValue: Double
|
||||
override val title: String,
|
||||
override val defaultValue: Double,
|
||||
val valueRange: ClosedFloatingPointRange<Double> = 0.0..100.0,
|
||||
) : SettingOption<Double>
|
||||
|
||||
data class BooleanSetting(
|
||||
override val key: String,
|
||||
override val title: String,
|
||||
override val defaultValue: Boolean
|
||||
) : SettingOption<Boolean>
|
||||
|
||||
data class StringSetting(
|
||||
override val key: String,
|
||||
override val title: String,
|
||||
override val defaultValue: String
|
||||
) : SettingOption<String>
|
||||
|
||||
object SettingsOptions {
|
||||
val defaultPlaybackSpeed = NumberSetting(
|
||||
key = "default_playback_speed",
|
||||
title = "Default playback speed",
|
||||
defaultValue = 1.0,
|
||||
valueRange = 0.5..2.0
|
||||
)
|
||||
|
||||
val confirmMobileDataPlayback = BooleanSetting(
|
||||
key = "confirm_mobile_data_playback",
|
||||
title = "Confirm mobile data playback",
|
||||
defaultValue = true
|
||||
)
|
||||
|
||||
val preferredAudioLanguage = StringSetting(
|
||||
key = "preferred_audio_language",
|
||||
title = "Preferred audio language",
|
||||
defaultValue = "English"
|
||||
)
|
||||
|
||||
val numberSettings = listOf(defaultPlaybackSpeed)
|
||||
val booleanSettings = listOf(confirmMobileDataPlayback)
|
||||
val stringSettings = listOf(preferredAudioLanguage)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user