mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-24 03:36:51 +00:00
feat(settings): add app update settings action
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package hu.bbara.purefin.ui.screen.settings
|
package hu.bbara.purefin.ui.screen.settings
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -24,6 +25,7 @@ import androidx.compose.material3.Slider
|
|||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -31,6 +33,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
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.platform.LocalContext
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
@@ -39,7 +42,6 @@ import hu.bbara.purefin.core.settings.BooleanSetting
|
|||||||
import hu.bbara.purefin.core.settings.DropdownSetting
|
import hu.bbara.purefin.core.settings.DropdownSetting
|
||||||
import hu.bbara.purefin.core.settings.RangeSetting
|
import hu.bbara.purefin.core.settings.RangeSetting
|
||||||
import hu.bbara.purefin.core.settings.SettingOption
|
import hu.bbara.purefin.core.settings.SettingOption
|
||||||
import hu.bbara.purefin.core.settings.SettingsOptions
|
|
||||||
import hu.bbara.purefin.core.settings.StringSetting
|
import hu.bbara.purefin.core.settings.StringSetting
|
||||||
import hu.bbara.purefin.core.settings.VoidSetting
|
import hu.bbara.purefin.core.settings.VoidSetting
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -50,6 +52,15 @@ fun TvSettingsScreen(
|
|||||||
onBack: () -> Unit = viewModel::onBack,
|
onBack: () -> Unit = viewModel::onBack,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val settingGroups by viewModel.settingGroups.collectAsState()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
LaunchedEffect(viewModel, context) {
|
||||||
|
viewModel.snackbarMessages.collect { message ->
|
||||||
|
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
@@ -72,7 +83,7 @@ fun TvSettingsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsOptions.groups.forEach { group ->
|
settingGroups.forEach { group ->
|
||||||
group.title?.let { title ->
|
group.title?.let { title ->
|
||||||
item {
|
item {
|
||||||
Text(
|
Text(
|
||||||
@@ -135,7 +146,7 @@ private fun TvSettingOptionItem(
|
|||||||
is VoidSetting -> {
|
is VoidSetting -> {
|
||||||
TvVoidSettingItem(
|
TvVoidSettingItem(
|
||||||
title = option.title,
|
title = option.title,
|
||||||
onClick = {}
|
onClick = { viewModel.onClick(option) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,14 @@ import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
|||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
@@ -21,7 +25,6 @@ import hu.bbara.purefin.core.settings.BooleanSetting
|
|||||||
import hu.bbara.purefin.core.settings.DropdownSetting
|
import hu.bbara.purefin.core.settings.DropdownSetting
|
||||||
import hu.bbara.purefin.core.settings.RangeSetting
|
import hu.bbara.purefin.core.settings.RangeSetting
|
||||||
import hu.bbara.purefin.core.settings.SettingOption
|
import hu.bbara.purefin.core.settings.SettingOption
|
||||||
import hu.bbara.purefin.core.settings.SettingsOptions
|
|
||||||
import hu.bbara.purefin.core.settings.StringSetting
|
import hu.bbara.purefin.core.settings.StringSetting
|
||||||
import hu.bbara.purefin.core.settings.VoidSetting
|
import hu.bbara.purefin.core.settings.VoidSetting
|
||||||
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
|
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
|
||||||
@@ -37,10 +40,20 @@ fun SettingsScreen(
|
|||||||
viewModel: SettingsViewModel = hiltViewModel(),
|
viewModel: SettingsViewModel = hiltViewModel(),
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val settingGroups by viewModel.settingGroups.collectAsState()
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(viewModel) {
|
||||||
|
viewModel.snackbarMessages.collect { message ->
|
||||||
|
snackbarHostState.showSnackbar(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
topBar = {
|
topBar = {
|
||||||
SettingsTopBar(onBack = viewModel::onBack)
|
SettingsTopBar(onBack = viewModel::onBack)
|
||||||
}
|
}
|
||||||
@@ -59,7 +72,7 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsOptions.groups.forEach { group ->
|
settingGroups.forEach { group ->
|
||||||
group.title?.let { title ->
|
group.title?.let { title ->
|
||||||
item {
|
item {
|
||||||
Text(
|
Text(
|
||||||
@@ -122,7 +135,7 @@ private fun SettingOptionItem(
|
|||||||
is VoidSetting -> {
|
is VoidSetting -> {
|
||||||
VoidSettingItem(
|
VoidSettingItem(
|
||||||
title = option.title,
|
title = option.title,
|
||||||
onClick = {}
|
onClick = { viewModel.onClick(option) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import hu.bbara.purefin.core.data.HomeRepository
|
|||||||
import hu.bbara.purefin.core.data.LocalMediaRepository
|
import hu.bbara.purefin.core.data.LocalMediaRepository
|
||||||
import hu.bbara.purefin.core.data.UserSessionRepository
|
import hu.bbara.purefin.core.data.UserSessionRepository
|
||||||
import hu.bbara.purefin.core.download.MediaDownloadController
|
import hu.bbara.purefin.core.download.MediaDownloadController
|
||||||
|
import hu.bbara.purefin.core.feature.update.AppUpdateController
|
||||||
import hu.bbara.purefin.core.jellyfin.JellyfinMediaMetadataUpdater
|
import hu.bbara.purefin.core.jellyfin.JellyfinMediaMetadataUpdater
|
||||||
import hu.bbara.purefin.core.model.EpisodeUiModel
|
import hu.bbara.purefin.core.model.EpisodeUiModel
|
||||||
import hu.bbara.purefin.core.model.LibraryUiModel
|
import hu.bbara.purefin.core.model.LibraryUiModel
|
||||||
@@ -40,6 +41,7 @@ class AppViewModel @Inject constructor(
|
|||||||
private val jellyfinMediaMetadataUpdater: JellyfinMediaMetadataUpdater,
|
private val jellyfinMediaMetadataUpdater: JellyfinMediaMetadataUpdater,
|
||||||
private val navigationManager: NavigationManager,
|
private val navigationManager: NavigationManager,
|
||||||
private val mediaDownloadManager: MediaDownloadController,
|
private val mediaDownloadManager: MediaDownloadController,
|
||||||
|
private val appUpdateController: AppUpdateController,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _isRefreshing = MutableStateFlow(false)
|
private val _isRefreshing = MutableStateFlow(false)
|
||||||
@@ -239,6 +241,10 @@ class AppViewModel @Inject constructor(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
homeRepository.refreshHomeData()
|
homeRepository.refreshHomeData()
|
||||||
|
appUpdateController.checkForUpdates(
|
||||||
|
showUpToDateMessage = false,
|
||||||
|
showFailureMessage = false
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Refresh is best-effort; don't crash on failure
|
// Refresh is best-effort; don't crash on failure
|
||||||
}
|
}
|
||||||
@@ -255,6 +261,10 @@ class AppViewModel @Inject constructor(
|
|||||||
_isRefreshing.value = true
|
_isRefreshing.value = true
|
||||||
try {
|
try {
|
||||||
homeRepository.refreshHomeData()
|
homeRepository.refreshHomeData()
|
||||||
|
appUpdateController.checkForUpdates(
|
||||||
|
showUpToDateMessage = false,
|
||||||
|
showFailureMessage = false
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Refresh is best-effort; don't crash on failure
|
// Refresh is best-effort; don't crash on failure
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -7,8 +7,20 @@ import hu.bbara.purefin.core.navigation.NavigationManager
|
|||||||
import hu.bbara.purefin.core.settings.BooleanSetting
|
import hu.bbara.purefin.core.settings.BooleanSetting
|
||||||
import hu.bbara.purefin.core.settings.DropdownSetting
|
import hu.bbara.purefin.core.settings.DropdownSetting
|
||||||
import hu.bbara.purefin.core.settings.RangeSetting
|
import hu.bbara.purefin.core.settings.RangeSetting
|
||||||
|
import hu.bbara.purefin.core.settings.SettingGroup
|
||||||
|
import hu.bbara.purefin.core.settings.SettingsGroupProvider
|
||||||
import hu.bbara.purefin.core.settings.SettingsRepository
|
import hu.bbara.purefin.core.settings.SettingsRepository
|
||||||
|
import hu.bbara.purefin.core.settings.SettingsOptions
|
||||||
import hu.bbara.purefin.core.settings.StringSetting
|
import hu.bbara.purefin.core.settings.StringSetting
|
||||||
|
import hu.bbara.purefin.core.settings.VoidSetting
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -16,8 +28,30 @@ import javax.inject.Inject
|
|||||||
class SettingsViewModel @Inject constructor(
|
class SettingsViewModel @Inject constructor(
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
private val navigationManager: NavigationManager,
|
private val navigationManager: NavigationManager,
|
||||||
|
settingsGroupProviders: Set<@JvmSuppressWildcards SettingsGroupProvider>,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _snackbarMessages = MutableSharedFlow<String>()
|
||||||
|
val snackbarMessages: SharedFlow<String> = _snackbarMessages.asSharedFlow()
|
||||||
|
|
||||||
|
private val dynamicSettingGroups = settingsGroupProviders
|
||||||
|
.map { it.settingGroups }
|
||||||
|
.takeIf { it.isNotEmpty() }
|
||||||
|
?.let { groupFlows ->
|
||||||
|
combine(groupFlows) { groups -> groups.toList().flatten() }
|
||||||
|
}
|
||||||
|
?: flowOf(emptyList())
|
||||||
|
|
||||||
|
val settingGroups: StateFlow<List<SettingGroup>> = dynamicSettingGroups
|
||||||
|
.combine(flowOf(SettingsOptions.groups)) { dynamicGroups, staticGroups ->
|
||||||
|
staticGroups + dynamicGroups
|
||||||
|
}
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(5_000),
|
||||||
|
initialValue = SettingsOptions.groups
|
||||||
|
)
|
||||||
|
|
||||||
fun value(option: RangeSetting) = settingsRepository.value(option)
|
fun value(option: RangeSetting) = settingsRepository.value(option)
|
||||||
|
|
||||||
fun value(option: BooleanSetting) = settingsRepository.value(option)
|
fun value(option: BooleanSetting) = settingsRepository.value(option)
|
||||||
@@ -50,6 +84,16 @@ class SettingsViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onClick(option: VoidSetting) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
option.onClick()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_snackbarMessages.emit(e.message ?: "Setting action failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onBack() {
|
fun onBack() {
|
||||||
navigationManager.pop()
|
navigationManager.pop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package hu.bbara.purefin.core.feature.update
|
||||||
|
|
||||||
|
import hu.bbara.purefin.core.settings.SettingGroup
|
||||||
|
import hu.bbara.purefin.core.settings.SettingsGroupProvider
|
||||||
|
import hu.bbara.purefin.core.settings.VoidSetting
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class AppUpdateController @Inject constructor(
|
||||||
|
private val appUpdateRepository: AppUpdateRepository,
|
||||||
|
private val appUpdateInstaller: AppUpdateInstaller
|
||||||
|
) : SettingsGroupProvider {
|
||||||
|
private val _isCheckingForUpdates = MutableStateFlow(false)
|
||||||
|
val isCheckingForUpdates: StateFlow<Boolean> = _isCheckingForUpdates.asStateFlow()
|
||||||
|
|
||||||
|
private val _availableUpdate = MutableStateFlow<AppUpdateInfo?>(null)
|
||||||
|
val availableUpdate: StateFlow<AppUpdateInfo?> = _availableUpdate.asStateFlow()
|
||||||
|
|
||||||
|
private val _snackbarMessages = MutableSharedFlow<String>()
|
||||||
|
val snackbarMessages: SharedFlow<String> = _snackbarMessages.asSharedFlow()
|
||||||
|
|
||||||
|
private val checkMutex = Mutex()
|
||||||
|
private val installMutex = Mutex()
|
||||||
|
|
||||||
|
override val settingGroups: Flow<List<SettingGroup>> = availableUpdate
|
||||||
|
.map { update ->
|
||||||
|
if (update == null) {
|
||||||
|
emptyList()
|
||||||
|
} else {
|
||||||
|
listOf(
|
||||||
|
SettingGroup(
|
||||||
|
title = "App",
|
||||||
|
options = listOf(installUpdateSetting(update))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun checkForUpdates(
|
||||||
|
showUpToDateMessage: Boolean = true,
|
||||||
|
showFailureMessage: Boolean = true
|
||||||
|
) {
|
||||||
|
if (!checkMutex.tryLock()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_isCheckingForUpdates.value = true
|
||||||
|
try {
|
||||||
|
val update = appUpdateRepository.checkForUpdate()
|
||||||
|
_availableUpdate.value = update
|
||||||
|
if (update == null && showUpToDateMessage) {
|
||||||
|
_snackbarMessages.emit("Purefin is up to date")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (showFailureMessage) {
|
||||||
|
_snackbarMessages.emit(e.message ?: "Update check failed")
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_isCheckingForUpdates.value = false
|
||||||
|
checkMutex.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun installAvailableUpdate() {
|
||||||
|
val update = _availableUpdate.value ?: return
|
||||||
|
if (!installMutex.tryLock()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_availableUpdate.value = null
|
||||||
|
try {
|
||||||
|
_snackbarMessages.emit(appUpdateInstaller.installUpdate(update))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_snackbarMessages.emit(e.message ?: "Update install failed")
|
||||||
|
} finally {
|
||||||
|
installMutex.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun declineUpdate() {
|
||||||
|
_availableUpdate.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installUpdateSetting(update: AppUpdateInfo): VoidSetting {
|
||||||
|
val versionLabel = update.versionName?.takeIf { it.isNotBlank() } ?: update.versionCode.toString()
|
||||||
|
return VoidSetting(
|
||||||
|
key = INSTALL_APP_UPDATE_KEY,
|
||||||
|
title = "Install Purefin $versionLabel",
|
||||||
|
onClick = { installAvailableUpdate() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val INSTALL_APP_UPDATE_KEY = "install_app_update"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,76 +3,35 @@ package hu.bbara.purefin.core.feature.update
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AppUpdateViewModel @Inject constructor(
|
class AppUpdateViewModel @Inject constructor(
|
||||||
private val appUpdateRepository: AppUpdateRepository,
|
private val appUpdateController: AppUpdateController
|
||||||
private val appUpdateInstaller: AppUpdateInstaller
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _isCheckingForUpdates = MutableStateFlow(false)
|
val isCheckingForUpdates: StateFlow<Boolean> = appUpdateController.isCheckingForUpdates
|
||||||
val isCheckingForUpdates: StateFlow<Boolean> = _isCheckingForUpdates.asStateFlow()
|
|
||||||
|
|
||||||
private val _availableUpdate = MutableStateFlow<AppUpdateInfo?>(null)
|
val availableUpdate: StateFlow<AppUpdateInfo?> = appUpdateController.availableUpdate
|
||||||
val availableUpdate: StateFlow<AppUpdateInfo?> = _availableUpdate.asStateFlow()
|
|
||||||
|
|
||||||
private val _snackbarMessages = MutableSharedFlow<String>()
|
val snackbarMessages: SharedFlow<String> = appUpdateController.snackbarMessages
|
||||||
val snackbarMessages: SharedFlow<String> = _snackbarMessages.asSharedFlow()
|
|
||||||
|
|
||||||
private var isInstallingUpdate = false
|
|
||||||
|
|
||||||
fun checkForUpdates(showUpToDateMessage: Boolean = true) {
|
fun checkForUpdates(showUpToDateMessage: Boolean = true) {
|
||||||
if (_isCheckingForUpdates.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_isCheckingForUpdates.value = true
|
appUpdateController.checkForUpdates(showUpToDateMessage = showUpToDateMessage)
|
||||||
try {
|
|
||||||
val update = appUpdateRepository.checkForUpdate()
|
|
||||||
if (update == null) {
|
|
||||||
if (showUpToDateMessage) {
|
|
||||||
_snackbarMessages.emit("Purefin is up to date")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_availableUpdate.value = update
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_snackbarMessages.emit(e.message ?: "Update check failed")
|
|
||||||
} finally {
|
|
||||||
_isCheckingForUpdates.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun acceptUpdate() {
|
fun acceptUpdate() {
|
||||||
val update = _availableUpdate.value ?: return
|
|
||||||
if (isInstallingUpdate) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
isInstallingUpdate = true
|
appUpdateController.installAvailableUpdate()
|
||||||
_availableUpdate.value = null
|
|
||||||
try {
|
|
||||||
_snackbarMessages.emit(appUpdateInstaller.installUpdate(update))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_snackbarMessages.emit(e.message ?: "Update install failed")
|
|
||||||
} finally {
|
|
||||||
isInstallingUpdate = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun declineUpdate() {
|
fun declineUpdate() {
|
||||||
_availableUpdate.value = null
|
appUpdateController.declineUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ data class StringSetting(
|
|||||||
data class VoidSetting(
|
data class VoidSetting(
|
||||||
override val key: String,
|
override val key: String,
|
||||||
override val title: String,
|
override val title: String,
|
||||||
override val defaultValue: Unit = Unit
|
override val defaultValue: Unit = Unit,
|
||||||
|
val onClick: suspend () -> Unit = {}
|
||||||
) : SettingOption<Unit>
|
) : SettingOption<Unit>
|
||||||
|
|
||||||
data class DropdownSetting<T>(
|
data class DropdownSetting<T>(
|
||||||
@@ -44,18 +45,6 @@ data class SettingGroup(
|
|||||||
)
|
)
|
||||||
|
|
||||||
object SettingsOptions {
|
object SettingsOptions {
|
||||||
val defaultPlaybackSpeed = RangeSetting(
|
|
||||||
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 autoPlayNextMedia = BooleanSetting(
|
val autoPlayNextMedia = BooleanSetting(
|
||||||
key = "auto_play_next_media",
|
key = "auto_play_next_media",
|
||||||
@@ -63,34 +52,11 @@ object SettingsOptions {
|
|||||||
defaultValue = true
|
defaultValue = true
|
||||||
)
|
)
|
||||||
|
|
||||||
val preferredAudioLanguage = StringSetting(
|
|
||||||
key = "preferred_audio_language",
|
|
||||||
title = "Preferred audio language",
|
|
||||||
defaultValue = "English"
|
|
||||||
)
|
|
||||||
|
|
||||||
val resetPlaybackSettings = VoidSetting(
|
|
||||||
key = "reset_playback_settings",
|
|
||||||
title = "Reset playback settings"
|
|
||||||
)
|
|
||||||
|
|
||||||
val streamingQuality = DropdownSetting(
|
|
||||||
key = "streaming_quality",
|
|
||||||
title = "Streaming quality",
|
|
||||||
defaultValue = "Auto",
|
|
||||||
options = listOf("Auto", "Low", "Medium", "High")
|
|
||||||
)
|
|
||||||
|
|
||||||
val groups = listOf(
|
val groups = listOf(
|
||||||
SettingGroup(
|
SettingGroup(
|
||||||
title = "Playback",
|
title = "Playback",
|
||||||
options = listOf(
|
options = listOf(
|
||||||
defaultPlaybackSpeed,
|
|
||||||
confirmMobileDataPlayback,
|
|
||||||
autoPlayNextMedia,
|
autoPlayNextMedia,
|
||||||
preferredAudioLanguage,
|
|
||||||
resetPlaybackSettings,
|
|
||||||
streamingQuality
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package hu.bbara.purefin.core.settings
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface SettingsGroupProvider {
|
||||||
|
val settingGroups: Flow<List<SettingGroup>>
|
||||||
|
}
|
||||||
@@ -4,8 +4,11 @@ import dagger.Binds
|
|||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import dagger.multibindings.IntoSet
|
||||||
|
import hu.bbara.purefin.core.feature.update.AppUpdateController
|
||||||
import hu.bbara.purefin.core.feature.update.AppUpdateInstaller
|
import hu.bbara.purefin.core.feature.update.AppUpdateInstaller
|
||||||
import hu.bbara.purefin.core.feature.update.AppVersionProvider
|
import hu.bbara.purefin.core.feature.update.AppVersionProvider
|
||||||
|
import hu.bbara.purefin.core.settings.SettingsGroupProvider
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
@@ -16,4 +19,8 @@ abstract class AppUpdateModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindAppVersionProvider(impl: AndroidAppVersionProvider): AppVersionProvider
|
abstract fun bindAppVersionProvider(impl: AndroidAppVersionProvider): AppVersionProvider
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoSet
|
||||||
|
abstract fun bindAppUpdateSettingsProvider(impl: AppUpdateController): SettingsGroupProvider
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user