refactor: settings viewmodel
This commit is contained in:
parent
2ab62e629f
commit
b0c567bdc9
13 changed files with 359 additions and 444 deletions
|
|
@ -10,7 +10,6 @@ import android.graphics.Bitmap
|
|||
|
||||
typealias CoverImage = Bitmap
|
||||
typealias Selected = Boolean
|
||||
typealias ID = Int
|
||||
typealias BottomSheet = String
|
||||
typealias Dialog = String
|
||||
typealias Drawer = String
|
||||
|
|
@ -40,6 +40,7 @@ import ua.acclorite.book_story.ui.common.helpers.ProvideSettings
|
|||
import ua.acclorite.book_story.ui.main.MainActivityKeyboardManager
|
||||
import ua.acclorite.book_story.ui.navigator.Navigator
|
||||
import ua.acclorite.book_story.ui.navigator.NavigatorTabs
|
||||
import ua.acclorite.book_story.ui.settings.SettingsEffects
|
||||
import ua.acclorite.book_story.ui.theme.BookStoryTheme
|
||||
import ua.acclorite.book_story.ui.theme.Transitions
|
||||
import java.lang.reflect.Field
|
||||
|
|
@ -56,7 +57,7 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen().setKeepOnScreenCondition {
|
||||
!settings.initialized.value || !settingsModel.isReady.value
|
||||
!settings.initialized.value || !settingsModel.initialized.value
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -80,6 +81,10 @@ class MainActivity : AppCompatActivity() {
|
|||
val historyModel = hiltViewModel<HistoryModel>()
|
||||
val browseModel = hiltViewModel<BrowseModel>()
|
||||
|
||||
SettingsEffects(
|
||||
effects = settingsModel.effects
|
||||
)
|
||||
|
||||
ProvideSettings(settings) {
|
||||
val tabs = persistentListOf(
|
||||
NavigatorItem(
|
||||
|
|
|
|||
|
|
@ -438,8 +438,7 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
|
|||
letterSpacing = letterSpacing,
|
||||
paragraphIndentation = paragraphIndentation,
|
||||
doubleClickTranslation = settings.doubleClickTranslation.value,
|
||||
selectPreviousPreset = settingsModel::onEvent,
|
||||
selectNextPreset = settingsModel::onEvent,
|
||||
switchColorPreset = settingsModel::onEvent,
|
||||
leave = screenModel::onEvent,
|
||||
restoreCheckpoint = screenModel::onEvent,
|
||||
scroll = screenModel::onEvent,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Book's Story — free and open-source Material You eBook reader.
|
||||
* Copyright (C) 2024-2025 Acclorite
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
package ua.acclorite.book_story.presentation.settings
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.Immutable
|
||||
import ua.acclorite.book_story.domain.model.reader.ColorPreset
|
||||
|
||||
@Immutable
|
||||
sealed class SettingsEffect {
|
||||
data class OnScroll(
|
||||
val listState: LazyListState,
|
||||
val index: Int,
|
||||
val offset: Int
|
||||
) : SettingsEffect()
|
||||
|
||||
data class OnSwitchedColorPreset(
|
||||
val newColorPreset: ColorPreset
|
||||
) : SettingsEffect()
|
||||
}
|
||||
|
|
@ -6,11 +6,8 @@
|
|||
|
||||
package ua.acclorite.book_story.presentation.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import ua.acclorite.book_story.core.ID
|
||||
import ua.acclorite.book_story.core.language.Language
|
||||
import ua.acclorite.book_story.domain.model.library.Category
|
||||
import ua.acclorite.book_story.presentation.library.model.LibrarySortOrder
|
||||
|
|
@ -22,11 +19,11 @@ sealed class SettingsEvent {
|
|||
) : SettingsEvent()
|
||||
|
||||
data class OnGrantPersistableUriPermission(
|
||||
val uri: Uri
|
||||
val uri: String
|
||||
) : SettingsEvent()
|
||||
|
||||
data class OnReleasePersistableUriPermission(
|
||||
val uri: Uri
|
||||
val uri: String
|
||||
) : SettingsEvent()
|
||||
|
||||
data class OnCreateCategory(
|
||||
|
|
@ -53,28 +50,24 @@ sealed class SettingsEvent {
|
|||
) : SettingsEvent()
|
||||
|
||||
data class OnSelectColorPreset(
|
||||
val id: ID
|
||||
val id: Int,
|
||||
) : SettingsEvent()
|
||||
|
||||
data class OnSelectPreviousPreset(
|
||||
val context: Context
|
||||
) : SettingsEvent()
|
||||
|
||||
data class OnSelectNextPreset(
|
||||
val context: Context
|
||||
data class OnSwitchColorPreset(
|
||||
val previous: Boolean
|
||||
) : SettingsEvent()
|
||||
|
||||
data class OnDeleteColorPreset(
|
||||
val id: ID
|
||||
val id: Int
|
||||
) : SettingsEvent()
|
||||
|
||||
data class OnUpdateColorPresetTitle(
|
||||
val id: ID,
|
||||
val id: Int,
|
||||
val title: String
|
||||
) : SettingsEvent()
|
||||
|
||||
data class OnShuffleColorPreset(
|
||||
val id: ID
|
||||
val id: Int
|
||||
) : SettingsEvent()
|
||||
|
||||
data class OnAddColorPreset(
|
||||
|
|
@ -82,20 +75,16 @@ sealed class SettingsEvent {
|
|||
val fontColor: Color
|
||||
) : SettingsEvent()
|
||||
|
||||
data class OnUpdateColorPresetColor(
|
||||
val id: Int,
|
||||
val backgroundColor: Color?,
|
||||
val fontColor: Color?
|
||||
) : SettingsEvent()
|
||||
|
||||
data class OnReorderColorPresets(
|
||||
val from: Int,
|
||||
val to: Int
|
||||
) : SettingsEvent()
|
||||
|
||||
data class OnUpdateColorPresetColor(
|
||||
val id: ID,
|
||||
val backgroundColor: Color?,
|
||||
val fontColor: Color?
|
||||
) : SettingsEvent()
|
||||
|
||||
data object OnConfirmReorderColorPresets : SettingsEvent()
|
||||
|
||||
data class OnScrollToColorPreset(
|
||||
val index: Int
|
||||
) : SettingsEvent()
|
||||
}
|
||||
|
|
@ -6,20 +6,20 @@
|
|||
|
||||
package ua.acclorite.book_story.presentation.settings
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.yield
|
||||
import ua.acclorite.book_story.R
|
||||
import ua.acclorite.book_story.domain.model.library.CategorySort
|
||||
import ua.acclorite.book_story.domain.model.reader.ColorPreset
|
||||
import ua.acclorite.book_story.domain.use_case.category.AddCategoryUseCase
|
||||
|
|
@ -37,8 +37,8 @@ import ua.acclorite.book_story.domain.use_case.color_preset.UpdateColorPresetUse
|
|||
import ua.acclorite.book_story.domain.use_case.permission.GrantPersistableUriPermissionUseCase
|
||||
import ua.acclorite.book_story.domain.use_case.permission.ReleasePersistableUriPermissionUseCase
|
||||
import ua.acclorite.book_story.domain.use_case.settings.UpdateLanguageUseCase
|
||||
import ua.acclorite.book_story.ui.common.helpers.showToast
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlin.random.Random
|
||||
|
||||
@HiltViewModel
|
||||
|
|
@ -65,75 +65,64 @@ class SettingsModel @Inject constructor(
|
|||
private val _state = MutableStateFlow(SettingsState())
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
private val _isReady = MutableStateFlow(false)
|
||||
val isReady = _isReady.asStateFlow()
|
||||
private val _effects = MutableSharedFlow<SettingsEffect>()
|
||||
val effects = _effects.asSharedFlow()
|
||||
|
||||
private var selectColorPresetJob: Job? = null
|
||||
private var addColorPresetJob: Job? = null
|
||||
private var deleteColorPresetJob: Job? = null
|
||||
private var updateColorColorPresetJob: Job? = null
|
||||
private var updateTitleColorPresetJob: Job? = null
|
||||
private var shuffleColorPresetJob: Job? = null
|
||||
private val _initialized = MutableStateFlow(false)
|
||||
val initialized = _initialized.asStateFlow()
|
||||
|
||||
private var colorPresetJob: Job? = null
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
viewModelScope.launch {
|
||||
var colorPresets = getColorPresetsUseCase()
|
||||
|
||||
if (colorPresets.isEmpty()) {
|
||||
updateColorPresetUseCase(ColorPreset.default)
|
||||
getColorPresetsUseCase().first().select()
|
||||
getColorPresetsUseCase().first().selectColorPreset()
|
||||
colorPresets = getColorPresetsUseCase()
|
||||
}
|
||||
|
||||
val scrollIndex = colorPresets.indexOfFirst {
|
||||
it.isSelected
|
||||
}
|
||||
val scrollIndex = colorPresets.indexOfFirst { it.isSelected }
|
||||
if (scrollIndex != -1) {
|
||||
launch(Dispatchers.Main) {
|
||||
try {
|
||||
_state.value.colorPresetListState.requestScrollToItem(index = scrollIndex)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
_effects.emit(
|
||||
SettingsEffect.OnScroll(
|
||||
listState = _state.value.colorPresetListState,
|
||||
index = scrollIndex,
|
||||
offset = 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
selectedColorPreset = colorPresets.selected(),
|
||||
selectedColorPreset = colorPresets.getSelectedColorPreset(),
|
||||
colorPresets = colorPresets,
|
||||
categories = getCategoriesUseCase(), // Getting categories
|
||||
categories = getCategoriesUseCase(),
|
||||
categoriesSort = getCategorySortingUseCase()
|
||||
)
|
||||
}
|
||||
|
||||
Log.i("SETTINGS", "SettingsModel is ready.")
|
||||
_isReady.update { true }
|
||||
_initialized.update { true }
|
||||
}
|
||||
}
|
||||
|
||||
fun onEvent(event: SettingsEvent) {
|
||||
viewModelScope.launch {
|
||||
when (event) {
|
||||
is SettingsEvent.OnUpdateLanguage -> updateLanguageUseCase(event.language)
|
||||
is SettingsEvent.OnUpdateLanguage -> {
|
||||
updateLanguageUseCase(event.language)
|
||||
}
|
||||
|
||||
is SettingsEvent.OnGrantPersistableUriPermission -> {
|
||||
viewModelScope.launch {
|
||||
grantPersistableUriPermissionUseCase(
|
||||
event.uri.toString()
|
||||
)
|
||||
}
|
||||
grantPersistableUriPermissionUseCase(event.uri)
|
||||
}
|
||||
|
||||
is SettingsEvent.OnReleasePersistableUriPermission -> {
|
||||
viewModelScope.launch {
|
||||
releasePersistableUriPermissionUseCase(
|
||||
event.uri.toString()
|
||||
)
|
||||
}
|
||||
releasePersistableUriPermissionUseCase(event.uri)
|
||||
}
|
||||
|
||||
is SettingsEvent.OnCreateCategory -> {
|
||||
viewModelScope.launch {
|
||||
addCategoryUseCase(event.title)
|
||||
_state.update {
|
||||
it.copy(
|
||||
|
|
@ -141,10 +130,8 @@ class SettingsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SettingsEvent.OnUpdateCategoryTitle -> {
|
||||
viewModelScope.launch {
|
||||
updateCategoryUseCase(
|
||||
categoryId = event.id,
|
||||
newTitle = event.title
|
||||
|
|
@ -155,10 +142,8 @@ class SettingsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SettingsEvent.OnUpdateCategoryOrder -> {
|
||||
viewModelScope.launch {
|
||||
updateCategoriesOrderUseCase(
|
||||
categories = event.categories
|
||||
)
|
||||
|
|
@ -168,10 +153,8 @@ class SettingsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SettingsEvent.OnRemoveCategory -> {
|
||||
viewModelScope.launch {
|
||||
deleteCategoryUseCase(
|
||||
category = event.category
|
||||
)
|
||||
|
|
@ -181,10 +164,8 @@ class SettingsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SettingsEvent.OnUpdateCategorySort -> {
|
||||
viewModelScope.launch {
|
||||
updateCategorySortingUseCase(
|
||||
categorySort = CategorySort(
|
||||
categoryId = event.categoryId,
|
||||
|
|
@ -198,24 +179,23 @@ class SettingsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SettingsEvent.OnSelectColorPreset -> {
|
||||
viewModelScope.launch {
|
||||
cancelColorPresetJobs()
|
||||
selectColorPresetJob = launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
colorPresetJob?.join()
|
||||
colorPresetJob = viewModelScope.launch(Dispatchers.Default) {
|
||||
val colorPreset = _state.value.colorPresets.getColorPresetById(event.id)
|
||||
?: return@launch
|
||||
|
||||
yield()
|
||||
ensureActive()
|
||||
|
||||
colorPreset.select(animate = true)
|
||||
colorPreset.selectColorPreset(animate = true)
|
||||
val colorPresets = _state.value.colorPresets.map {
|
||||
it.copy(isSelected = colorPreset.id == it.id)
|
||||
}
|
||||
_state.update {
|
||||
it.copy(
|
||||
selectedColorPreset = colorPresets.selected(),
|
||||
selectedColorPreset = colorPresets.getSelectedColorPreset(),
|
||||
colorPresets = colorPresets
|
||||
)
|
||||
}
|
||||
|
|
@ -223,162 +203,88 @@ class SettingsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
is SettingsEvent.OnSelectPreviousPreset -> {
|
||||
viewModelScope.launch {
|
||||
cancelColorPresetJobs()
|
||||
selectColorPresetJob = launch {
|
||||
is SettingsEvent.OnSwitchColorPreset -> {
|
||||
withContext(Dispatchers.IO) {
|
||||
colorPresetJob?.join()
|
||||
colorPresetJob = viewModelScope.launch(Dispatchers.Default) {
|
||||
val colorPresets = _state.value.colorPresets
|
||||
if (colorPresets.size == 1) return@launch
|
||||
|
||||
val selectedPreset = _state.value.selectedColorPreset
|
||||
|
||||
if (colorPresets.size == 1) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
val selectedPresetIndex = colorPresets.indexOf(selectedPreset)
|
||||
if (selectedPresetIndex == -1) {
|
||||
return@launch
|
||||
}
|
||||
if (selectedPresetIndex == -1) return@launch
|
||||
|
||||
val previousColorPresetIndex = when (selectedPresetIndex) {
|
||||
0 -> {
|
||||
colorPresets.lastIndex
|
||||
val newColorPresetIndex = if (event.previous) {
|
||||
when (selectedPresetIndex) {
|
||||
0 -> colorPresets.lastIndex
|
||||
else -> selectedPresetIndex - 1
|
||||
}
|
||||
|
||||
else -> {
|
||||
selectedPresetIndex - 1
|
||||
} else {
|
||||
when (selectedPresetIndex) {
|
||||
colorPresets.lastIndex -> 0
|
||||
else -> selectedPresetIndex + 1
|
||||
}
|
||||
}
|
||||
val previousColorPreset = colorPresets.getOrNull(previousColorPresetIndex)
|
||||
val newColorPreset = colorPresets.getOrNull(newColorPresetIndex)
|
||||
?: return@launch
|
||||
|
||||
yield()
|
||||
ensureActive()
|
||||
|
||||
previousColorPreset.select()
|
||||
newColorPreset.selectColorPreset()
|
||||
val updatedColorPresets = _state.value.colorPresets.map {
|
||||
it.copy(isSelected = previousColorPreset.id == it.id)
|
||||
it.copy(isSelected = newColorPreset.id == it.id)
|
||||
}
|
||||
_state.update {
|
||||
it.copy(
|
||||
selectedColorPreset = updatedColorPresets.selected(),
|
||||
selectedColorPreset = updatedColorPresets.getSelectedColorPreset(),
|
||||
colorPresets = updatedColorPresets
|
||||
)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
event.context.getString(
|
||||
R.string.color_preset_selected_query,
|
||||
if (previousColorPreset.name.isNullOrBlank()) {
|
||||
event.context.getString(
|
||||
R.string.color_preset_query,
|
||||
previousColorPreset.id.toString()
|
||||
)
|
||||
} else {
|
||||
previousColorPreset.name
|
||||
}.trim()
|
||||
).showToast(event.context, longToast = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SettingsEvent.OnSelectNextPreset -> {
|
||||
viewModelScope.launch {
|
||||
cancelColorPresetJobs()
|
||||
selectColorPresetJob = launch {
|
||||
val colorPresets = _state.value.colorPresets
|
||||
val selectedPreset = _state.value.selectedColorPreset
|
||||
|
||||
if (colorPresets.size == 1) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
val selectedPresetIndex = colorPresets.indexOf(selectedPreset)
|
||||
if (selectedPresetIndex == -1) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
val nextColorPresetIndex = when (selectedPresetIndex) {
|
||||
colorPresets.lastIndex -> {
|
||||
0
|
||||
}
|
||||
|
||||
else -> {
|
||||
selectedPresetIndex + 1
|
||||
}
|
||||
}
|
||||
val nextColorPreset = colorPresets.getOrNull(nextColorPresetIndex)
|
||||
?: return@launch
|
||||
|
||||
yield()
|
||||
|
||||
nextColorPreset.select()
|
||||
val updatedColorPresets = _state.value.colorPresets.map {
|
||||
it.copy(isSelected = nextColorPreset.id == it.id)
|
||||
}
|
||||
_state.update {
|
||||
it.copy(
|
||||
selectedColorPreset = updatedColorPresets.selected(),
|
||||
colorPresets = updatedColorPresets
|
||||
)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
event.context.getString(
|
||||
R.string.color_preset_selected_query,
|
||||
if (nextColorPreset.name.isNullOrBlank()) {
|
||||
event.context.getString(
|
||||
R.string.color_preset_query,
|
||||
nextColorPreset.id.toString()
|
||||
)
|
||||
} else {
|
||||
nextColorPreset.name
|
||||
}.trim()
|
||||
).showToast(event.context, longToast = false)
|
||||
}
|
||||
_effects.emit(SettingsEffect.OnSwitchedColorPreset(newColorPreset))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SettingsEvent.OnDeleteColorPreset -> {
|
||||
viewModelScope.launch {
|
||||
cancelColorPresetJobs()
|
||||
deleteColorPresetJob = launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
colorPresetJob?.join()
|
||||
colorPresetJob = viewModelScope.launch(Dispatchers.Default) {
|
||||
if (_state.value.colorPresets.size == 1) return@launch
|
||||
val colorPreset = _state.value.colorPresets.getColorPresetById(event.id)
|
||||
?: return@launch
|
||||
|
||||
yield()
|
||||
|
||||
val position = _state.value.colorPresets.indexOf(colorPreset)
|
||||
if (position == -1) {
|
||||
return@launch
|
||||
}
|
||||
if (position == -1) return@launch
|
||||
|
||||
val nextPosition = if (position == _state.value.colorPresets.lastIndex) {
|
||||
val nextPosition =
|
||||
if (position == _state.value.colorPresets.lastIndex) {
|
||||
position - 1
|
||||
} else {
|
||||
position
|
||||
}
|
||||
|
||||
yield()
|
||||
ensureActive()
|
||||
|
||||
deleteColorPresetUseCase(colorPreset)
|
||||
val nextColorPreset = getColorPresetsUseCase().getOrNull(nextPosition)
|
||||
?: return@launch
|
||||
|
||||
nextColorPreset.select()
|
||||
nextColorPreset.selectColorPreset()
|
||||
val colorPresets = getColorPresetsUseCase()
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
selectedColorPreset = colorPresets.selected(),
|
||||
selectedColorPreset = colorPresets.getSelectedColorPreset(),
|
||||
colorPresets = colorPresets
|
||||
)
|
||||
}
|
||||
|
||||
onEvent(
|
||||
SettingsEvent.OnScrollToColorPreset(
|
||||
index = nextPosition
|
||||
_effects.emit(
|
||||
SettingsEffect.OnScroll(
|
||||
listState = _state.value.colorPresetListState,
|
||||
index = nextPosition,
|
||||
offset = 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -386,19 +292,14 @@ class SettingsModel @Inject constructor(
|
|||
}
|
||||
|
||||
is SettingsEvent.OnUpdateColorPresetTitle -> {
|
||||
viewModelScope.launch {
|
||||
cancelColorPresetJobs()
|
||||
updateTitleColorPresetJob = launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
colorPresetJob?.join()
|
||||
colorPresetJob = viewModelScope.launch(Dispatchers.Default) {
|
||||
val colorPreset = _state.value.colorPresets.getColorPresetById(event.id)
|
||||
?: return@launch
|
||||
val updatedColorPreset = colorPreset.copy(name = event.title)
|
||||
|
||||
yield()
|
||||
|
||||
val updatedColorPreset = colorPreset.copy(
|
||||
name = event.title
|
||||
)
|
||||
|
||||
yield()
|
||||
ensureActive()
|
||||
|
||||
updateColorPresetUseCase(updatedColorPreset)
|
||||
_state.update {
|
||||
|
|
@ -414,14 +315,11 @@ class SettingsModel @Inject constructor(
|
|||
}
|
||||
|
||||
is SettingsEvent.OnShuffleColorPreset -> {
|
||||
viewModelScope.launch {
|
||||
cancelColorPresetJobs()
|
||||
shuffleColorPresetJob = launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
colorPresetJob?.join()
|
||||
colorPresetJob = viewModelScope.launch(Dispatchers.Default) {
|
||||
val colorPreset = _state.value.colorPresets.getColorPresetById(event.id)
|
||||
?: return@launch
|
||||
|
||||
yield()
|
||||
|
||||
val shuffledColorPreset = colorPreset.copy(
|
||||
backgroundColor = colorPreset.backgroundColor.copy(
|
||||
red = Random.nextFloat(),
|
||||
|
|
@ -435,7 +333,7 @@ class SettingsModel @Inject constructor(
|
|||
)
|
||||
)
|
||||
|
||||
yield()
|
||||
ensureActive()
|
||||
|
||||
updateColorPresetUseCase(shuffledColorPreset)
|
||||
_state.update {
|
||||
|
|
@ -451,41 +349,44 @@ class SettingsModel @Inject constructor(
|
|||
}
|
||||
|
||||
is SettingsEvent.OnAddColorPreset -> {
|
||||
viewModelScope.launch {
|
||||
cancelColorPresetJobs()
|
||||
addColorPresetJob = launch {
|
||||
yield()
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
colorPresetJob?.join()
|
||||
colorPresetJob = viewModelScope.launch(Dispatchers.Default) {
|
||||
val newColorPreset = ColorPreset.default.copy(
|
||||
backgroundColor = event.backgroundColor,
|
||||
fontColor = event.fontColor
|
||||
)
|
||||
updateColorPresetUseCase(newColorPreset)
|
||||
|
||||
getColorPresetsUseCase().last().select()
|
||||
ensureActive()
|
||||
|
||||
updateColorPresetUseCase(newColorPreset)
|
||||
getColorPresetsUseCase().last().selectColorPreset()
|
||||
val colorPresets = getColorPresetsUseCase()
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
selectedColorPreset = colorPresets.selected(),
|
||||
selectedColorPreset = colorPresets.getSelectedColorPreset(),
|
||||
colorPresets = colorPresets
|
||||
)
|
||||
}
|
||||
|
||||
onEvent(SettingsEvent.OnScrollToColorPreset(colorPresets.lastIndex))
|
||||
_effects.emit(
|
||||
SettingsEffect.OnScroll(
|
||||
listState = _state.value.colorPresetListState,
|
||||
index = colorPresets.lastIndex,
|
||||
offset = 0
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SettingsEvent.OnUpdateColorPresetColor -> {
|
||||
viewModelScope.launch {
|
||||
cancelColorPresetJobs()
|
||||
updateColorColorPresetJob = launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
colorPresetJob?.join()
|
||||
colorPresetJob = viewModelScope.launch(Dispatchers.Default) {
|
||||
val colorPreset = _state.value.colorPresets.getColorPresetById(event.id)
|
||||
?: return@launch
|
||||
|
||||
yield()
|
||||
|
||||
val updatedColorPreset = colorPreset.copy(
|
||||
backgroundColor = event.backgroundColor
|
||||
?: colorPreset.backgroundColor,
|
||||
|
|
@ -493,7 +394,7 @@ class SettingsModel @Inject constructor(
|
|||
?: colorPreset.fontColor
|
||||
)
|
||||
|
||||
yield()
|
||||
ensureActive()
|
||||
|
||||
updateColorPresetUseCase(updatedColorPreset)
|
||||
_state.update {
|
||||
|
|
@ -509,13 +410,13 @@ class SettingsModel @Inject constructor(
|
|||
}
|
||||
|
||||
is SettingsEvent.OnReorderColorPresets -> {
|
||||
viewModelScope.launch {
|
||||
cancelColorPresetJobs()
|
||||
launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
colorPresetJob?.join()
|
||||
withContext(Dispatchers.Default) {
|
||||
val reorderedColorPresets = _state.value.colorPresets
|
||||
.toMutableList()
|
||||
.apply {
|
||||
add(event.to, removeAt(event.from))
|
||||
.also { colorPresets ->
|
||||
colorPresets.add(event.to, colorPresets.removeAt(event.from))
|
||||
}
|
||||
|
||||
_state.update {
|
||||
|
|
@ -528,29 +429,18 @@ class SettingsModel @Inject constructor(
|
|||
}
|
||||
|
||||
is SettingsEvent.OnConfirmReorderColorPresets -> {
|
||||
viewModelScope.launch {
|
||||
cancelColorPresetJobs()
|
||||
launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
colorPresetJob?.join()
|
||||
withContext(Dispatchers.Default) {
|
||||
reorderColorPresetsUseCase(_state.value.colorPresets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SettingsEvent.OnScrollToColorPreset -> {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_state.value.colorPresetListState.requestScrollToItem(
|
||||
index = event.index
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ColorPreset.select(animate: Boolean = false) {
|
||||
private suspend fun ColorPreset.selectColorPreset(animate: Boolean = false) {
|
||||
selectColorPresetUseCase(this)
|
||||
_state.update {
|
||||
it.copy(
|
||||
|
|
@ -560,53 +450,28 @@ class SettingsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun List<ColorPreset>.getColorPresetById(id: Int): ColorPreset? {
|
||||
return firstOrNull {
|
||||
return find {
|
||||
it.id == id
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<ColorPreset>?.selected(): ColorPreset {
|
||||
val presets = this ?: _state.value.colorPresets
|
||||
|
||||
if (presets.size == 1) {
|
||||
return presets.first()
|
||||
}
|
||||
|
||||
val selectedPreset = presets.firstOrNull { it.isSelected }
|
||||
|
||||
if (selectedPreset == null) {
|
||||
private fun List<ColorPreset>.getSelectedColorPreset(): ColorPreset {
|
||||
if (size == 1) return first()
|
||||
find { it.isSelected }?.also { return it }
|
||||
return ColorPreset.default
|
||||
}
|
||||
|
||||
return selectedPreset
|
||||
}
|
||||
|
||||
private fun List<ColorPreset>.updateColorPreset(colorPreset: ColorPreset): List<ColorPreset> {
|
||||
if (size == 1) {
|
||||
return listOf(colorPreset)
|
||||
if (size == 1) return listOf(colorPreset)
|
||||
return map {
|
||||
if (it.isSelected) colorPreset
|
||||
else it
|
||||
}
|
||||
|
||||
return this.map {
|
||||
if (it.isSelected) {
|
||||
colorPreset
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelColorPresetJobs() {
|
||||
selectColorPresetJob?.cancel()
|
||||
addColorPresetJob?.cancel()
|
||||
updateTitleColorPresetJob?.cancel()
|
||||
shuffleColorPresetJob?.cancel()
|
||||
updateColorColorPresetJob?.cancel()
|
||||
deleteColorPresetJob?.cancel()
|
||||
}
|
||||
|
||||
private suspend inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
|
||||
mutex.withLock {
|
||||
yield()
|
||||
coroutineContext.ensureActive()
|
||||
this.value = function(this.value)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,11 +28,10 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.lerp
|
||||
import ua.acclorite.book_story.core.ID
|
||||
import ua.acclorite.book_story.ui.common.components.common.AnimatedVisibility
|
||||
|
||||
data class TopAppBarData(
|
||||
val contentID: ID,
|
||||
val contentID: Int,
|
||||
val contentNavigationIcon: @Composable () -> Unit,
|
||||
val contentTitle: @Composable () -> Unit,
|
||||
val contentActions: @Composable RowScope.() -> Unit
|
||||
|
|
@ -46,7 +45,7 @@ data class TopAppBarData(
|
|||
* @param scrolledContainerColor Scrolled container color of the TopBar.
|
||||
* @param scrollBehavior [TopAppBarScrollBehavior].
|
||||
* @param isTopBarScrolled Whether isScrolled state should be forced or not.
|
||||
* @param shownTopBar [ID] of the top bar to show.
|
||||
* @param shownTopBar ID of the top bar to show.
|
||||
* @param topBars Pass a list of all [TopAppBarData] to show.
|
||||
* @param customContent Custom content below [TopAppBar].
|
||||
*/
|
||||
|
|
@ -59,7 +58,7 @@ fun TopAppBar(
|
|||
scrollBehavior: TopAppBarScrollBehavior?,
|
||||
isTopBarScrolled: Boolean?,
|
||||
|
||||
shownTopBar: ID,
|
||||
shownTopBar: Int,
|
||||
topBars: List<TopAppBarData>,
|
||||
customContent: @Composable ColumnScope.() -> Unit = {}
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -12,17 +12,14 @@ import androidx.compose.runtime.mutableFloatStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import ua.acclorite.book_story.presentation.settings.SettingsEvent
|
||||
|
||||
@Composable
|
||||
fun Modifier.readerColorPresetChange(
|
||||
colorPresetChangeEnabled: Boolean,
|
||||
isLoading: Boolean,
|
||||
selectPreviousPreset: (SettingsEvent.OnSelectPreviousPreset) -> Unit,
|
||||
selectNextPreset: (SettingsEvent.OnSelectNextPreset) -> Unit
|
||||
switchColorPreset: (SettingsEvent.OnSwitchColorPreset) -> Unit
|
||||
): Modifier {
|
||||
val context = LocalContext.current
|
||||
val offset = remember { mutableFloatStateOf(0f) }
|
||||
return this.then(
|
||||
if (colorPresetChangeEnabled && !isLoading) {
|
||||
|
|
@ -33,17 +30,17 @@ fun Modifier.readerColorPresetChange(
|
|||
onDragEnd = {
|
||||
when {
|
||||
offset.floatValue > 200 -> {
|
||||
selectPreviousPreset(
|
||||
SettingsEvent.OnSelectPreviousPreset(
|
||||
context = context
|
||||
switchColorPreset(
|
||||
SettingsEvent.OnSwitchColorPreset(
|
||||
previous = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
offset.floatValue < -200 -> {
|
||||
selectNextPreset(
|
||||
SettingsEvent.OnSelectNextPreset(
|
||||
context = context
|
||||
switchColorPreset(
|
||||
SettingsEvent.OnSwitchColorPreset(
|
||||
previous = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,8 +88,7 @@ fun ReaderContent(
|
|||
letterSpacing: TextUnit,
|
||||
paragraphIndentation: TextUnit,
|
||||
doubleClickTranslation: Boolean,
|
||||
selectPreviousPreset: (SettingsEvent.OnSelectPreviousPreset) -> Unit,
|
||||
selectNextPreset: (SettingsEvent.OnSelectNextPreset) -> Unit,
|
||||
switchColorPreset: (SettingsEvent.OnSwitchColorPreset) -> Unit,
|
||||
menuVisibility: (ReaderEvent.OnMenuVisibility) -> Unit,
|
||||
leave: (ReaderEvent.OnLeave) -> Unit,
|
||||
restoreCheckpoint: (ReaderEvent.OnRestoreCheckpoint) -> Unit,
|
||||
|
|
@ -165,8 +164,7 @@ fun ReaderContent(
|
|||
letterSpacing = letterSpacing,
|
||||
paragraphIndentation = paragraphIndentation,
|
||||
doubleClickTranslation = doubleClickTranslation,
|
||||
selectPreviousPreset = selectPreviousPreset,
|
||||
selectNextPreset = selectNextPreset,
|
||||
switchColorPreset = switchColorPreset,
|
||||
menuVisibility = menuVisibility,
|
||||
leave = leave,
|
||||
restoreCheckpoint = restoreCheckpoint,
|
||||
|
|
|
|||
|
|
@ -92,8 +92,7 @@ fun ReaderScaffold(
|
|||
letterSpacing: TextUnit,
|
||||
paragraphIndentation: TextUnit,
|
||||
doubleClickTranslation: Boolean,
|
||||
selectPreviousPreset: (SettingsEvent.OnSelectPreviousPreset) -> Unit,
|
||||
selectNextPreset: (SettingsEvent.OnSelectNextPreset) -> Unit,
|
||||
switchColorPreset: (SettingsEvent.OnSwitchColorPreset) -> Unit,
|
||||
menuVisibility: (ReaderEvent.OnMenuVisibility) -> Unit,
|
||||
leave: (ReaderEvent.OnLeave) -> Unit,
|
||||
restoreCheckpoint: (ReaderEvent.OnRestoreCheckpoint) -> Unit,
|
||||
|
|
@ -127,8 +126,7 @@ fun ReaderScaffold(
|
|||
isLoading = isLoading,
|
||||
lockMenu = lockMenu,
|
||||
leave = leave,
|
||||
selectPreviousPreset = selectPreviousPreset,
|
||||
selectNextPreset = selectNextPreset,
|
||||
switchColorPreset = switchColorPreset,
|
||||
showSettingsBottomSheet = showSettingsBottomSheet,
|
||||
showChaptersDrawer = showChaptersDrawer,
|
||||
navigateBack = navigateBack,
|
||||
|
|
|
|||
|
|
@ -49,8 +49,7 @@ fun ReaderTopBar(
|
|||
isLoading: Boolean,
|
||||
lockMenu: Boolean,
|
||||
leave: (ReaderEvent.OnLeave) -> Unit,
|
||||
selectPreviousPreset: (SettingsEvent.OnSelectPreviousPreset) -> Unit,
|
||||
selectNextPreset: (SettingsEvent.OnSelectNextPreset) -> Unit,
|
||||
switchColorPreset: (SettingsEvent.OnSwitchColorPreset) -> Unit,
|
||||
showSettingsBottomSheet: (ReaderEvent.OnShowSettingsBottomSheet) -> Unit,
|
||||
showChaptersDrawer: (ReaderEvent.OnShowChaptersDrawer) -> Unit,
|
||||
navigateToBookInfo: (ReaderEvent.OnNavigateToBookInfo) -> Unit,
|
||||
|
|
@ -68,8 +67,7 @@ fun ReaderTopBar(
|
|||
.readerColorPresetChange(
|
||||
colorPresetChangeEnabled = fastColorPresetChange,
|
||||
isLoading = isLoading,
|
||||
selectPreviousPreset = selectPreviousPreset,
|
||||
selectNextPreset = selectNextPreset
|
||||
switchColorPreset = switchColorPreset
|
||||
)
|
||||
) {
|
||||
TopAppBar(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Book's Story — free and open-source Material You eBook reader.
|
||||
* Copyright (C) 2024-2025 Acclorite
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
package ua.acclorite.book_story.ui.settings
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import ua.acclorite.book_story.R
|
||||
import ua.acclorite.book_story.presentation.settings.SettingsEffect
|
||||
import ua.acclorite.book_story.ui.common.helpers.showToast
|
||||
|
||||
@Composable
|
||||
fun SettingsEffects(effects: SharedFlow<SettingsEffect>) {
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(effects) {
|
||||
effects.collect { effect ->
|
||||
when (effect) {
|
||||
is SettingsEffect.OnScroll -> {
|
||||
effect.listState.requestScrollToItem(effect.index, effect.offset)
|
||||
}
|
||||
|
||||
is SettingsEffect.OnSwitchedColorPreset -> {
|
||||
context.getString(
|
||||
R.string.color_preset_selected_query,
|
||||
if (effect.newColorPreset.name.isNullOrBlank()) {
|
||||
context.getString(
|
||||
R.string.color_preset_query,
|
||||
effect.newColorPreset.id.toString()
|
||||
)
|
||||
} else {
|
||||
effect.newColorPreset.name
|
||||
}.trim()
|
||||
).showToast(context, longToast = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ fun BrowseScanOption() {
|
|||
if (uri == null) return@rememberLauncherForActivityResult
|
||||
settingsModel.onEvent(
|
||||
SettingsEvent.OnGrantPersistableUriPermission(
|
||||
uri = uri
|
||||
uri = uri.toString()
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ fun BrowseScanOption() {
|
|||
releasePersistableUriPermission = {
|
||||
settingsModel.onEvent(
|
||||
SettingsEvent.OnReleasePersistableUriPermission(
|
||||
uri = permission.uri
|
||||
uri = permission.uri.toString()
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue