refactor: datastore(settings) refactor

This commit is contained in:
Acclorite 2025-07-17 12:14:52 +03:00
parent aed85c0d6a
commit b4c6a63878
No known key found for this signature in database
GPG key ID: 6E54C611F6EE8593
135 changed files with 1173 additions and 2844 deletions

View file

@ -8,4 +8,13 @@ package ua.acclorite.book_story.core.helpers
fun <T> MutableList<T>.addAll(calculation: () -> List<T>) { fun <T> MutableList<T>.addAll(calculation: () -> List<T>) {
addAll(calculation()) addAll(calculation())
}
fun <T> List<T>.toggle(item: T): List<T> {
return toMutableList().apply { toggle(item) }
}
fun <T> MutableList<T>.toggle(item: T) {
if (contains(item)) remove(item)
else add(item)
} }

View file

@ -31,14 +31,12 @@ import ua.acclorite.book_story.data.parser.TextParserImpl
import ua.acclorite.book_story.data.repository.BookRepositoryImpl import ua.acclorite.book_story.data.repository.BookRepositoryImpl
import ua.acclorite.book_story.data.repository.CategoryRepositoryImpl import ua.acclorite.book_story.data.repository.CategoryRepositoryImpl
import ua.acclorite.book_story.data.repository.ColorPresetRepositoryImpl import ua.acclorite.book_story.data.repository.ColorPresetRepositoryImpl
import ua.acclorite.book_story.data.repository.DataStoreRepositoryImpl
import ua.acclorite.book_story.data.repository.FileSystemRepositoryImpl import ua.acclorite.book_story.data.repository.FileSystemRepositoryImpl
import ua.acclorite.book_story.data.repository.HistoryRepositoryImpl import ua.acclorite.book_story.data.repository.HistoryRepositoryImpl
import ua.acclorite.book_story.data.repository.PermissionRepositoryImpl import ua.acclorite.book_story.data.repository.PermissionRepositoryImpl
import ua.acclorite.book_story.domain.repository.BookRepository import ua.acclorite.book_story.domain.repository.BookRepository
import ua.acclorite.book_story.domain.repository.CategoryRepository import ua.acclorite.book_story.domain.repository.CategoryRepository
import ua.acclorite.book_story.domain.repository.ColorPresetRepository import ua.acclorite.book_story.domain.repository.ColorPresetRepository
import ua.acclorite.book_story.domain.repository.DataStoreRepository
import ua.acclorite.book_story.domain.repository.FileSystemRepository import ua.acclorite.book_story.domain.repository.FileSystemRepository
import ua.acclorite.book_story.domain.repository.HistoryRepository import ua.acclorite.book_story.domain.repository.HistoryRepository
import ua.acclorite.book_story.domain.repository.PermissionRepository import ua.acclorite.book_story.domain.repository.PermissionRepository
@ -71,12 +69,6 @@ abstract class RepositoryModule {
colorPresetRepositoryImpl: ColorPresetRepositoryImpl colorPresetRepositoryImpl: ColorPresetRepositoryImpl
): ColorPresetRepository ): ColorPresetRepository
@Binds
@Singleton
abstract fun bindDataStoreRepository(
dataStoreRepositoryImpl: DataStoreRepositoryImpl
): DataStoreRepository
@Binds @Binds
@Singleton @Singleton
abstract fun bindFileSystemRepository( abstract fun bindFileSystemRepository(

View file

@ -10,6 +10,5 @@ import androidx.datastore.preferences.core.Preferences
interface DataStore { interface DataStore {
suspend fun <T> getNullableData(key: Preferences.Key<T>): T? suspend fun <T> getNullableData(key: Preferences.Key<T>): T?
suspend fun getAllData(): Set<Preferences.Key<*>>?
suspend fun <T> putData(key: Preferences.Key<T>, value: T) suspend fun <T> putData(key: Preferences.Key<T>, value: T)
} }

View file

@ -10,12 +10,8 @@ import android.app.Application
import android.content.Context import android.content.Context
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
private val Context.dataStore by preferencesDataStore("data_store") private val Context.dataStore by preferencesDataStore("data_store")
@ -23,30 +19,14 @@ private val Context.dataStore by preferencesDataStore("data_store")
class DataStoreImpl @Inject constructor(context: Application) : DataStore { class DataStoreImpl @Inject constructor(context: Application) : DataStore {
private val dataStore = context.dataStore private val dataStore = context.dataStore
/** override suspend fun <T> getNullableData(key: Preferences.Key<T>): T? {
* Gets data from DataStore by given [key]. If no such [key] exists, returns null. return try {
*/ dataStore.data.firstOrNull()?.get(key)
override suspend fun <T> getNullableData(key: Preferences.Key<T>): T? = } catch (_: Exception) {
dataStore.data.catch { exception -> null
if (exception is IOException) emit(emptyPreferences()) }
else throw exception
}.map { preferences ->
preferences[key]
}.firstOrNull()
/**
* Gets all keys from DataStore.
*/
override suspend fun getAllData(): Set<Preferences.Key<*>>? {
return dataStore.data
.map {
it.asMap().keys
}.firstOrNull()
} }
/**
* Puts data in DataStore by given [key].
*/
override suspend fun <T> putData(key: Preferences.Key<T>, value: T) { override suspend fun <T> putData(key: Preferences.Key<T>, value: T) {
dataStore.edit { preferences -> dataStore.edit { preferences ->
preferences[key] = value preferences[key] = value

View file

@ -8,7 +8,6 @@ package ua.acclorite.book_story.data.model.common
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import ua.acclorite.book_story.core.ui.UIText import ua.acclorite.book_story.core.ui.UIText
import ua.acclorite.book_story.data.model.library.BookWithCover
@Immutable @Immutable
sealed class NullableBook( sealed class NullableBook(

View file

@ -6,9 +6,8 @@
package ua.acclorite.book_story.data.parser package ua.acclorite.book_story.data.parser
import ua.acclorite.book_story.data.model.common.BookWithCover
import ua.acclorite.book_story.data.model.file.CachedFile import ua.acclorite.book_story.data.model.file.CachedFile
import ua.acclorite.book_story.data.model.library.BookWithCover
interface FileParser { interface FileParser {
suspend fun parse(cachedFile: CachedFile): BookWithCover? suspend fun parse(cachedFile: CachedFile): BookWithCover?

View file

@ -7,8 +7,8 @@
package ua.acclorite.book_story.data.parser package ua.acclorite.book_story.data.parser
import android.util.Log import android.util.Log
import ua.acclorite.book_story.data.model.common.BookWithCover
import ua.acclorite.book_story.data.model.file.CachedFile import ua.acclorite.book_story.data.model.file.CachedFile
import ua.acclorite.book_story.data.model.library.BookWithCover
import ua.acclorite.book_story.data.parser.epub.EpubFileParser import ua.acclorite.book_story.data.parser.epub.EpubFileParser
import ua.acclorite.book_story.data.parser.fb2.Fb2FileParser import ua.acclorite.book_story.data.parser.fb2.Fb2FileParser
import ua.acclorite.book_story.data.parser.html.HtmlFileParser import ua.acclorite.book_story.data.parser.html.HtmlFileParser

View file

@ -14,8 +14,8 @@ import org.jsoup.Jsoup
import org.jsoup.parser.Parser import org.jsoup.parser.Parser
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.core.ui.UIText import ua.acclorite.book_story.core.ui.UIText
import ua.acclorite.book_story.data.model.common.BookWithCover
import ua.acclorite.book_story.data.model.file.CachedFile import ua.acclorite.book_story.data.model.file.CachedFile
import ua.acclorite.book_story.data.model.library.BookWithCover
import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.library.Book import ua.acclorite.book_story.domain.model.library.Book
import java.io.File import java.io.File

View file

@ -10,8 +10,8 @@ import org.jsoup.Jsoup
import org.jsoup.parser.Parser import org.jsoup.parser.Parser
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.core.ui.UIText import ua.acclorite.book_story.core.ui.UIText
import ua.acclorite.book_story.data.model.common.BookWithCover
import ua.acclorite.book_story.data.model.file.CachedFile import ua.acclorite.book_story.data.model.file.CachedFile
import ua.acclorite.book_story.data.model.library.BookWithCover
import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.library.Book import ua.acclorite.book_story.domain.model.library.Book
import javax.inject.Inject import javax.inject.Inject

View file

@ -10,8 +10,8 @@ import org.jsoup.Jsoup
import org.jsoup.parser.Parser import org.jsoup.parser.Parser
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.core.ui.UIText import ua.acclorite.book_story.core.ui.UIText
import ua.acclorite.book_story.data.model.common.BookWithCover
import ua.acclorite.book_story.data.model.file.CachedFile import ua.acclorite.book_story.data.model.file.CachedFile
import ua.acclorite.book_story.data.model.library.BookWithCover
import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.library.Book import ua.acclorite.book_story.domain.model.library.Book
import javax.inject.Inject import javax.inject.Inject

View file

@ -11,8 +11,8 @@ import com.tom_roush.pdfbox.android.PDFBoxResourceLoader
import com.tom_roush.pdfbox.pdmodel.PDDocument import com.tom_roush.pdfbox.pdmodel.PDDocument
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.core.ui.UIText import ua.acclorite.book_story.core.ui.UIText
import ua.acclorite.book_story.data.model.common.BookWithCover
import ua.acclorite.book_story.data.model.file.CachedFile import ua.acclorite.book_story.data.model.file.CachedFile
import ua.acclorite.book_story.data.model.library.BookWithCover
import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.library.Book import ua.acclorite.book_story.domain.model.library.Book
import javax.inject.Inject import javax.inject.Inject

View file

@ -8,8 +8,8 @@ package ua.acclorite.book_story.data.parser.txt
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.core.ui.UIText import ua.acclorite.book_story.core.ui.UIText
import ua.acclorite.book_story.data.model.common.BookWithCover
import ua.acclorite.book_story.data.model.file.CachedFile import ua.acclorite.book_story.data.model.file.CachedFile
import ua.acclorite.book_story.data.model.library.BookWithCover
import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.library.Book import ua.acclorite.book_story.domain.model.library.Book
import javax.inject.Inject import javax.inject.Inject

View file

@ -13,10 +13,6 @@ import ua.acclorite.book_story.domain.repository.ColorPresetRepository
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
/**
* Color Preset repository.
* Manages all [ColorPreset] related work.
*/
@Singleton @Singleton
class ColorPresetRepositoryImpl @Inject constructor( class ColorPresetRepositoryImpl @Inject constructor(
private val database: BookDao, private val database: BookDao,

View file

@ -1,40 +0,0 @@
/*
* 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.data.repository
import androidx.datastore.preferences.core.Preferences
import ua.acclorite.book_story.data.local.data_store.DataStore
import ua.acclorite.book_story.domain.repository.DataStoreRepository
import javax.inject.Inject
import javax.inject.Singleton
/**
* Data Store repository.
* Manages all [DataStore] related work.
*/
@Singleton
class DataStoreRepositoryImpl @Inject constructor(
private val dataStore: DataStore
) : DataStoreRepository {
override suspend fun <T> putPreference(
key: Preferences.Key<T>,
value: T
): Result<Unit> = runCatching {
dataStore.putData(key, value)
}
override suspend fun <T> getPreference(
key: Preferences.Key<T>
): Result<T> = runCatching {
dataStore.getNullableData(key) ?: throw NoSuchElementException("Could not get preference.")
}
override suspend fun getAllPreferences(): Result<Set<Preferences.Key<*>>> = runCatching {
dataStore.getAllData() ?: throw NoSuchElementException("Could not get all preferences.")
}
}

View file

@ -9,8 +9,8 @@ package ua.acclorite.book_story.data.repository
import ua.acclorite.book_story.core.data.ExtensionsData import ua.acclorite.book_story.core.data.ExtensionsData
import ua.acclorite.book_story.data.local.room.BookDao import ua.acclorite.book_story.data.local.room.BookDao
import ua.acclorite.book_story.data.mapper.file.FileMapper import ua.acclorite.book_story.data.mapper.file.FileMapper
import ua.acclorite.book_story.data.model.common.BookWithCover
import ua.acclorite.book_story.data.model.file.CachedFile import ua.acclorite.book_story.data.model.file.CachedFile
import ua.acclorite.book_story.data.model.library.BookWithCover
import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.file.File import ua.acclorite.book_story.domain.model.file.File
import ua.acclorite.book_story.domain.repository.FileSystemRepository import ua.acclorite.book_story.domain.repository.FileSystemRepository

View file

@ -0,0 +1,352 @@
/*
* 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.data.settings
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.doublePreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ua.acclorite.book_story.core.data.CoreData
import ua.acclorite.book_story.core.log.logI
import ua.acclorite.book_story.data.local.data_store.DataStore
import ua.acclorite.book_story.data.settings.model.Setting
import ua.acclorite.book_story.presentation.browse.model.BrowseLayout
import ua.acclorite.book_story.presentation.browse.model.BrowseSortOrder
import ua.acclorite.book_story.presentation.library.model.LibraryLayout
import ua.acclorite.book_story.presentation.library.model.LibrarySortOrder
import ua.acclorite.book_story.presentation.library.model.LibraryTitlePosition
import ua.acclorite.book_story.presentation.reader.model.ReaderColorEffects
import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness
import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture
import ua.acclorite.book_story.presentation.reader.model.ReaderProgressCount
import ua.acclorite.book_story.presentation.reader.model.ReaderScreenOrientation
import ua.acclorite.book_story.presentation.reader.model.ReaderTextAlignment
import ua.acclorite.book_story.ui.reader.data.ReaderData
import ua.acclorite.book_story.ui.reader.model.FontWithName
import ua.acclorite.book_story.ui.theme.model.DarkTheme
import ua.acclorite.book_story.ui.theme.model.HorizontalAlignment
import ua.acclorite.book_story.ui.theme.model.PureDark
import ua.acclorite.book_story.ui.theme.model.Theme
import ua.acclorite.book_story.ui.theme.model.ThemeContrast
import java.util.Locale
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
import javax.inject.Singleton
@Suppress("UNCHECKED_CAST")
@Singleton
class SettingsManager @Inject constructor(
private val dataStore: DataStore
) {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val settingsCount = AtomicInteger(0)
private val initializedSettingsCount = AtomicInteger(0)
private val _initialized = MutableStateFlow(false)
val initialized = _initialized.asStateFlow()
/* ------ Settings --------------------------- */
/* ------ General ---------------------------- */
val language = setting<String, String>(
key = stringPreferencesKey("language"),
default = Locale.getDefault().language.take(2).let { locale ->
CoreData.languages.any { locale == it.first }.run {
if (this) locale
else "en"// Default language (English)
}
}
)
val theme = setting<Theme, String>(
key = stringPreferencesKey("theme"), default = Theme.Companion.entries().first(),
serialize = { it.name }, deserialize = { Theme.valueOf(it) }
)
val darkTheme = setting<DarkTheme, String>(
key = stringPreferencesKey("dark_theme"), default = DarkTheme.FOLLOW_SYSTEM,
serialize = { it.name }, deserialize = { DarkTheme.valueOf(it) }
)
val pureDark = setting<PureDark, String>(
key = stringPreferencesKey("pure_dark"), default = PureDark.OFF,
serialize = { it.name }, deserialize = { PureDark.valueOf(it) }
)
val absoluteDark = setting<Boolean, Boolean>(
key = booleanPreferencesKey("absolute_dark"), default = false
)
val themeContrast = setting<ThemeContrast, String>(
key = stringPreferencesKey("theme_contrast"), default = ThemeContrast.STANDARD,
serialize = { it.name }, deserialize = { ThemeContrast.valueOf(it) }
)
val showStartScreen = setting<Boolean, Boolean>(
key = booleanPreferencesKey("guide"), default = true
)
val doublePressExit = setting<Boolean, Boolean>(
key = booleanPreferencesKey("double_press_exit"), default = false
)
/* ------ Reader ----------------------------- */
val fontFamily = setting<FontWithName, String>(
key = stringPreferencesKey("font"), default = ReaderData.fonts[0],
serialize = { it.id }, deserialize = { id ->
ReaderData.fonts.find { it.id == id } ?: ReaderData.fonts[0]
}
)
val fontThickness = setting<ReaderFontThickness, String>(
key = stringPreferencesKey("font_thickness"), default = ReaderFontThickness.NORMAL,
serialize = { it.name }, deserialize = { ReaderFontThickness.valueOf(it) }
)
val italic = setting<Boolean, Boolean>(
key = booleanPreferencesKey("font_style"), default = false
)
val fontSize = setting<Int, Int>(
key = intPreferencesKey("font_size"), default = 16
)
val lineHeight = setting<Int, Int>(
key = intPreferencesKey("line_height"), default = 4
)
val paragraphHeight = setting<Int, Int>(
key = intPreferencesKey("paragraph_height"), default = 8
)
val paragraphIndentation = setting<Int, Int>(
key = intPreferencesKey("paragraph_indentation_int"), default = 0
)
val sidePadding = setting<Int, Int>(
key = intPreferencesKey("side_padding"), default = 6
)
val verticalPadding = setting<Int, Int>(
key = intPreferencesKey("vertical_padding"), default = 0
)
val doubleClickTranslation = setting<Boolean, Boolean>(
key = booleanPreferencesKey("double_click_translation"), default = false
)
val fastColorPresetChange = setting<Boolean, Boolean>(
key = booleanPreferencesKey("fast_color_preset_change"), default = true
)
val textAlignment = setting<ReaderTextAlignment, String>(
key = stringPreferencesKey("text_alignment"), default = ReaderTextAlignment.JUSTIFY,
serialize = { it.name }, deserialize = { ReaderTextAlignment.valueOf(it) }
)
val letterSpacing = setting<Int, Int>(
key = intPreferencesKey("letter_spacing"), default = 0
)
val cutoutPadding = setting<Boolean, Boolean>(
key = booleanPreferencesKey("cutout_padding"), default = false
)
val fullscreen = setting<Boolean, Boolean>(
key = booleanPreferencesKey("fullscreen"), default = true
)
val keepScreenOn = setting<Boolean, Boolean>(
key = booleanPreferencesKey("keep_screen_on"), default = true
)
val hideBarsOnFastScroll = setting<Boolean, Boolean>(
key = booleanPreferencesKey("hide_bars_on_fast_scroll"), default = false
)
val perceptionExpander = setting<Boolean, Boolean>(
key = booleanPreferencesKey("perception_expander"), default = false
)
val perceptionExpanderPadding = setting<Int, Int>(
key = intPreferencesKey("perception_expander_padding"), default = 5
)
val perceptionExpanderThickness = setting<Int, Int>(
key = intPreferencesKey("perception_expander_thickness"), default = 4
)
val screenOrientation = setting<ReaderScreenOrientation, String>(
key = stringPreferencesKey("screen_orientation"), default = ReaderScreenOrientation.DEFAULT,
serialize = { it.name }, deserialize = { ReaderScreenOrientation.valueOf(it) }
)
val customScreenBrightness = setting<Boolean, Boolean>(
key = booleanPreferencesKey("custom_screen_brightness"), default = false
)
val screenBrightness = setting<Float, Double>(
key = doublePreferencesKey("screen_brightness"), default = 0.5f,
serialize = { it.toDouble() }, deserialize = { it.toFloat() }
)
val horizontalGesture = setting<ReaderHorizontalGesture, String>(
key = stringPreferencesKey("horizontal_gesture"), default = ReaderHorizontalGesture.OFF,
serialize = { it.name }, deserialize = { ReaderHorizontalGesture.valueOf(it) }
)
val horizontalGestureScroll = setting<Float, Double>(
key = doublePreferencesKey("horizontal_gesture_scroll"), default = 0.7f,
serialize = { it.toDouble() }, deserialize = { it.toFloat() }
)
val horizontalGestureSensitivity = setting<Float, Double>(
key = doublePreferencesKey("horizontal_gesture_sensitivity"), default = 0.6f,
serialize = { it.toDouble() }, deserialize = { it.toFloat() }
)
val horizontalGestureAlphaAnim = setting<Boolean, Boolean>(
key = booleanPreferencesKey("horizontal_gesture_alpha_anim_bool"), default = true
)
val horizontalGesturePullAnim = setting<Boolean, Boolean>(
key = booleanPreferencesKey("horizontal_gesture_pull_anim"), default = true
)
val bottomBarPadding = setting<Int, Int>(
key = intPreferencesKey("bottom_bar_padding"), default = 0
)
val highlightedReading = setting<Boolean, Boolean>(
key = booleanPreferencesKey("highlighted_reading"), default = false
)
val highlightedReadingThickness = setting<Int, Int>(
key = intPreferencesKey("highlighted_reading_thickness"), default = 2
)
val chapterTitleAlignment = setting<ReaderTextAlignment, String>(
key = stringPreferencesKey("chapter_title_alignment"),
default = ReaderTextAlignment.JUSTIFY,
serialize = { it.name }, deserialize = { ReaderTextAlignment.valueOf(it) }
)
val images = setting<Boolean, Boolean>(
key = booleanPreferencesKey("images"), default = true
)
val imagesCaptions = setting<Boolean, Boolean>(
key = booleanPreferencesKey("images_captions"), default = true
)
val imagesCornersRoundness = setting<Int, Int>(
key = intPreferencesKey("images_corners_roundness"), default = 8
)
val imagesAlignment = setting<HorizontalAlignment, String>(
key = stringPreferencesKey("images_alignment"), default = HorizontalAlignment.START,
serialize = { it.name }, deserialize = { HorizontalAlignment.valueOf(it) }
)
val imagesWidth = setting<Float, Double>(
key = doublePreferencesKey("images_width"), default = 0.8f,
serialize = { it.toDouble() }, deserialize = { it.toFloat() }
)
val imagesColorEffects = setting<ReaderColorEffects, String>(
key = stringPreferencesKey("images_color_effects"), default = ReaderColorEffects.OFF,
serialize = { it.name }, deserialize = { ReaderColorEffects.valueOf(it) }
)
val progressBar = setting<Boolean, Boolean>(
key = booleanPreferencesKey("progress_bar"), default = false
)
val progressBarPadding = setting<Int, Int>(
key = intPreferencesKey("progress_bar_padding"), default = 4
)
val progressBarAlignment = setting<HorizontalAlignment, String>(
key = stringPreferencesKey("progress_bar_alignment"), default = HorizontalAlignment.CENTER,
serialize = { it.name }, deserialize = { HorizontalAlignment.valueOf(it) }
)
val progressBarFontSize = setting<Int, Int>(
key = intPreferencesKey("progress_bar_font_size"), default = 8
)
val progressCount = setting<ReaderProgressCount, String>(
key = stringPreferencesKey("progress_count"), default = ReaderProgressCount.PERCENTAGE,
serialize = { it.name }, deserialize = { ReaderProgressCount.valueOf(it) }
)
/* ------ Library ---------------------------- */
val libraryLayout = setting<LibraryLayout, String>(
key = stringPreferencesKey("library_layout"), default = LibraryLayout.GRID,
serialize = { it.name }, deserialize = { LibraryLayout.valueOf(it) }
)
val libraryAutoGridSize = setting<Boolean, Boolean>(
key = booleanPreferencesKey("library_auto_grid_size"), default = true
)
val libraryGridSize = setting<Int, Int>(
key = intPreferencesKey("library_grid_size"), default = 0
)
val libraryTitlePosition = setting<LibraryTitlePosition, String>(
key = stringPreferencesKey("library_title_position"), default = LibraryTitlePosition.BELOW,
serialize = { it.name }, deserialize = { LibraryTitlePosition.valueOf(it) }
)
val libraryShowReadButton = setting<Boolean, Boolean>(
key = booleanPreferencesKey("library_show_read_button"), default = true
)
val libraryShowProgress = setting<Boolean, Boolean>(
key = booleanPreferencesKey("library_show_progress"), default = true
)
val libraryShowBookCount = setting<Boolean, Boolean>(
key = booleanPreferencesKey("library_show_book_count"), default = true
)
val libraryShowCategoryTabs = setting<Boolean, Boolean>(
key = booleanPreferencesKey("library_show_category_tabs"), default = true
)
val libraryShowDefaultTab = setting<Boolean, Boolean>(
key = booleanPreferencesKey("library_show_default_tab"), default = false
)
val librarySortOrder = setting<LibrarySortOrder, String>(
key = stringPreferencesKey("library_sort_order"), default = LibrarySortOrder.LAST_READ,
serialize = { it.name }, deserialize = { LibrarySortOrder.valueOf(it) }
)
val librarySortOrderDescending = setting<Boolean, Boolean>(
key = booleanPreferencesKey("library_sort_order_descending"), default = true
)
val libraryPerCategorySort = setting<Boolean, Boolean>(
key = booleanPreferencesKey("library_per_category_sort"), default = false
)
/* ------ Browse ----------------------------- */
val browseLayout = setting<BrowseLayout, String>(
key = stringPreferencesKey("browse_layout"), default = BrowseLayout.LIST,
serialize = { it.name }, deserialize = { BrowseLayout.valueOf(it) }
)
val browseAutoGridSize = setting<Boolean, Boolean>(
key = booleanPreferencesKey("browse_auto_grid_size"), default = true
)
val browseGridSize = setting<Int, Int>(
key = intPreferencesKey("browse_grid_size"), default = 0
)
val browseSortOrder = setting<BrowseSortOrder, String>(
key = stringPreferencesKey("browse_sort_order"), default = BrowseSortOrder.LAST_MODIFIED,
serialize = { it.name }, deserialize = { BrowseSortOrder.valueOf(it) }
)
val browseSortOrderDescending = setting<Boolean, Boolean>(
key = booleanPreferencesKey("browse_sort_order_descending"), default = true
)
val browseIncludedFilterItems = setting<List<String>, Set<String>>(
key = stringSetPreferencesKey("browse_included_filter_items"), default = emptyList(),
serialize = { it.toSet() }, deserialize = { it.toList() }
)
val browsePinnedPaths = setting<List<String>, Set<String>>(
key = stringSetPreferencesKey("browse_pinned_paths"), default = emptyList(),
serialize = { it.toSet() }, deserialize = { it.toList() }
)
/* - - - - - - - - - - - - - - - - - - - - - - */
private fun <T, P> setting(
key: Preferences.Key<P>,
default: T,
serialize: (T) -> P = { it as P },
deserialize: (P) -> T = { it as T }
): Setting<T, P> {
settingsCount.incrementAndGet()
return Setting<T, P>(
key = key,
default = default,
setSetting = {
scope.launch {
logI("Updating setting: [${key.name}].")
dataStore.putData(key, it)
}
},
serialize = serialize,
deserialize = deserialize
).also { setting ->
scope.launch {
setting.init(dataStore.getNullableData<P>(key))
logI("Successfully initialized setting: [${key.name}].")
initializeSetting()
}
}
}
private fun initializeSetting() {
if (initializedSettingsCount.incrementAndGet() == settingsCount.get()) {
logI("Successfully initialized all $settingsCount settings.")
_initialized.update { true }
}
}
}

View file

@ -0,0 +1,39 @@
/*
* 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.data.settings.model
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.datastore.preferences.core.Preferences
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
@Stable
class Setting<T, P>(
val key: Preferences.Key<P>,
default: T,
private val setSetting: (P) -> Unit,
private val serialize: (T) -> P,
private val deserialize: (P) -> T
) {
private val _value = MutableStateFlow<T>(default)
val value: T
@Composable get() = _value.collectAsStateWithLifecycle().value
val lastValue: T
get() = _value.value
fun update(value: T) {
_value.update { value }
setSetting(serialize(value))
}
fun init(value: P?) {
if (value == null) return
_value.update { deserialize(value) }
}
}

View file

@ -1,22 +0,0 @@
/*
* 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.domain.repository
import androidx.datastore.preferences.core.Preferences
interface DataStoreRepository {
suspend fun <T> putPreference(
key: Preferences.Key<T>,
value: T
): Result<Unit>
suspend fun <T> getPreference(
key: Preferences.Key<T>,
): Result<T>
suspend fun getAllPreferences(): Result<Set<Preferences.Key<*>>>
}

View file

@ -6,7 +6,7 @@
package ua.acclorite.book_story.domain.repository package ua.acclorite.book_story.domain.repository
import ua.acclorite.book_story.data.model.library.BookWithCover import ua.acclorite.book_story.data.model.common.BookWithCover
import ua.acclorite.book_story.domain.model.file.File import ua.acclorite.book_story.domain.model.file.File
interface FileSystemRepository { interface FileSystemRepository {

View file

@ -1,39 +0,0 @@
/*
* 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.domain.use_case.data_store
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import ua.acclorite.book_story.core.log.logE
import ua.acclorite.book_story.core.log.logI
import ua.acclorite.book_story.domain.repository.DataStoreRepository
import ua.acclorite.book_story.presentation.main.data.DataStoreData
import javax.inject.Inject
class ChangeLanguagePreferenceUseCase @Inject constructor(
private val dataStoreRepository: DataStoreRepository
) {
suspend operator fun invoke(language: String) {
logI("Changing language preference.")
dataStoreRepository.putPreference(
DataStoreData.LANGUAGE,
language
).fold(
onSuccess = {
logI("Successfully changed language preference.")
val appLocale = LocaleListCompat.forLanguageTags(language)
AppCompatDelegate.setApplicationLocales(appLocale)
},
onFailure = {
logE("Could not change language preference with error: ${it.message}")
}
)
}
}

View file

@ -1,62 +0,0 @@
/*
* 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.domain.use_case.data_store
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.withContext
import ua.acclorite.book_story.core.log.logE
import ua.acclorite.book_story.core.log.logI
import ua.acclorite.book_story.core.log.logW
import ua.acclorite.book_story.domain.repository.DataStoreRepository
import ua.acclorite.book_story.presentation.main.MainState
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
class GetPreferencesUseCase @Inject constructor(
private val dataStoreRepository: DataStoreRepository
) {
suspend operator fun invoke(): MainState {
logI("Getting all preferences (MainState).")
return withContext(Dispatchers.Default) {
dataStoreRepository.getAllPreferences().mapCatching { keys ->
logI("Got [${keys.size}] preference keys.")
val preferences = ConcurrentHashMap<String, Any>()
keys.map { key ->
async {
dataStoreRepository.getPreference(key).fold(
onSuccess = {
it?.let { preference ->
preferences[key.name] = preference
}
},
onFailure = {
logW("Could not get [${key.name}] preference.")
preferences.remove(key.name)
}
)
}
}.awaitAll()
MainState.initialize(preferences)
}.fold(
onSuccess = {
logI("Successfully got all preferences (MainState).")
it
},
onFailure = {
logE("Could not get all preferences (MainState) with error: ${it.message}")
MainState()
}
)
}
}
}

View file

@ -1,31 +0,0 @@
/*
* 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.domain.use_case.data_store
import androidx.datastore.preferences.core.Preferences
import ua.acclorite.book_story.core.log.logE
import ua.acclorite.book_story.core.log.logI
import ua.acclorite.book_story.domain.repository.DataStoreRepository
import javax.inject.Inject
class PutPreferenceUseCase @Inject constructor(
private val dataStoreRepository: DataStoreRepository
) {
suspend operator fun <T> invoke(key: Preferences.Key<T>, value: T) {
logI("Putting preference [${key.name}].")
dataStoreRepository.putPreference(key, value).fold(
onSuccess = {
logI("Successfully put preference [${key.name}].")
},
onFailure = {
logE("Could not put preference [${key.name}] with error: ${it.message}")
}
)
}
}

View file

@ -0,0 +1,23 @@
/*
* 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.domain.use_case.settings
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import ua.acclorite.book_story.data.settings.SettingsManager
import javax.inject.Inject
class UpdateLanguageUseCase @Inject constructor(
private val settings: SettingsManager
) {
operator fun invoke(language: String) {
val appLocale = LocaleListCompat.forLanguageTags(language)
AppCompatDelegate.setApplicationLocales(appLocale)
settings.language.update(language)
}
}

View file

@ -14,7 +14,6 @@ import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
@ -24,12 +23,13 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import ua.acclorite.book_story.core.helpers.toggle
import ua.acclorite.book_story.presentation.browse.model.BrowseLayout import ua.acclorite.book_story.presentation.browse.model.BrowseLayout
import ua.acclorite.book_story.presentation.library.LibraryScreen import ua.acclorite.book_story.presentation.library.LibraryScreen
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.navigator.Screen import ua.acclorite.book_story.presentation.navigator.Screen
import ua.acclorite.book_story.presentation.settings.BrowseSettingsScreen import ua.acclorite.book_story.presentation.settings.BrowseSettingsScreen
import ua.acclorite.book_story.ui.browse.BrowseContent import ua.acclorite.book_story.ui.browse.BrowseContent
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.navigator.LocalNavigator import ua.acclorite.book_story.ui.navigator.LocalNavigator
@Parcelize @Parcelize
@ -64,10 +64,9 @@ object BrowseScreen : Screen, Parcelable {
override fun Content() { override fun Content() {
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val screenModel = hiltViewModel<BrowseModel>() val screenModel = hiltViewModel<BrowseModel>()
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = screenModel.state.collectAsStateWithLifecycle() val state = screenModel.state.collectAsStateWithLifecycle()
val mainState = mainModel.state.collectAsStateWithLifecycle()
val listState = rememberLazyListState(initialListIndex, initialListOffset) val listState = rememberLazyListState(initialListIndex, initialListOffset)
val gridState = rememberLazyGridState(initialGridIndex, initialGridOffset) val gridState = rememberLazyGridState(initialGridIndex, initialGridOffset)
@ -85,15 +84,18 @@ object BrowseScreen : Screen, Parcelable {
} }
) )
val files = remember { val files = remember(
derivedStateOf { state.value.files,
screenModel.filterList( settings.browseIncludedFilterItems.value,
files = state.value.files, settings.browseSortOrderDescending.value,
sortOrderDescending = mainState.value.browseSortOrderDescending, settings.browseSortOrder.value
includedFilterItems = mainState.value.browseIncludedFilterItems, ) {
sortOrder = mainState.value.browseSortOrder screenModel.filterList(
) files = state.value.files,
} sortOrderDescending = settings.browseSortOrderDescending.lastValue,
includedFilterItems = settings.browseIncludedFilterItems.lastValue,
sortOrder = settings.browseSortOrder.lastValue
)
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@ -112,7 +114,7 @@ object BrowseScreen : Screen, Parcelable {
initialGridIndex = 0 initialGridIndex = 0
initialGridOffset = 0 initialGridOffset = 0
when (mainState.value.browseLayout) { when (settings.browseLayout.lastValue) {
BrowseLayout.LIST -> { BrowseLayout.LIST -> {
initialListIndex = listState.firstVisibleItemIndex initialListIndex = listState.firstVisibleItemIndex
initialListOffset = listState.firstVisibleItemScrollOffset initialListOffset = listState.firstVisibleItemScrollOffset
@ -127,7 +129,7 @@ object BrowseScreen : Screen, Parcelable {
} }
BrowseContent( BrowseContent(
files = files.value, files = files,
selectedBooksAddDialog = state.value.selectedBooksAddDialog, selectedBooksAddDialog = state.value.selectedBooksAddDialog,
refreshState = refreshState, refreshState = refreshState,
loadingAddDialog = state.value.loadingAddDialog, loadingAddDialog = state.value.loadingAddDialog,
@ -135,11 +137,11 @@ object BrowseScreen : Screen, Parcelable {
bottomSheet = state.value.bottomSheet, bottomSheet = state.value.bottomSheet,
listState = listState, listState = listState,
gridState = gridState, gridState = gridState,
layout = mainState.value.browseLayout, layout = settings.browseLayout.value,
gridSize = mainState.value.browseGridSize, gridSize = settings.browseGridSize.value,
autoGridSize = mainState.value.browseAutoGridSize, autoGridSize = settings.browseAutoGridSize.value,
includedFilterItems = mainState.value.browseIncludedFilterItems, includedFilterItems = settings.browseIncludedFilterItems.value,
pinnedPaths = mainState.value.browsePinnedPaths, pinnedPaths = settings.browsePinnedPaths.value,
canScrollBackList = listState.canScrollBackward, canScrollBackList = listState.canScrollBackward,
canScrollBackGrid = gridState.canScrollBackward, canScrollBackGrid = gridState.canScrollBackward,
hasSelectedItems = state.value.hasSelectedItems, hasSelectedItems = state.value.hasSelectedItems,
@ -147,7 +149,7 @@ object BrowseScreen : Screen, Parcelable {
isRefreshing = state.value.isRefreshing, isRefreshing = state.value.isRefreshing,
isLoading = state.value.isLoading, isLoading = state.value.isLoading,
dialogHidden = state.value.dialog == null, dialogHidden = state.value.dialog == null,
filesEmpty = files.value.isEmpty(), filesEmpty = files.isEmpty(),
showSearch = state.value.showSearch, showSearch = state.value.showSearch,
searchQuery = state.value.searchQuery, searchQuery = state.value.searchQuery,
focusRequester = focusRequester, focusRequester = focusRequester,
@ -164,7 +166,11 @@ object BrowseScreen : Screen, Parcelable {
dismissAddDialog = screenModel::onEvent, dismissAddDialog = screenModel::onEvent,
selectAddDialog = screenModel::onEvent, selectAddDialog = screenModel::onEvent,
actionAddDialog = screenModel::onEvent, actionAddDialog = screenModel::onEvent,
changePinnedPaths = mainModel::onEvent, updatePinnedPaths = {
settings.browsePinnedPaths.update(
settings.browsePinnedPaths.lastValue.toggle(it)
)
},
navigateToLibrary = { navigateToLibrary = {
navigator.push(LibraryScreen, saveInBackStack = false) navigator.push(LibraryScreen, saveInBackStack = false)
}, },

View file

@ -12,9 +12,9 @@ import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import ua.acclorite.book_story.presentation.main.model.ThemeContrast
import ua.acclorite.book_story.ui.theme.BookStoryTheme import ua.acclorite.book_story.ui.theme.BookStoryTheme
import ua.acclorite.book_story.ui.theme.Theme import ua.acclorite.book_story.ui.theme.model.Theme
import ua.acclorite.book_story.ui.theme.model.ThemeContrast
class CrashActivity : AppCompatActivity() { class CrashActivity : AppCompatActivity() {

View file

@ -10,14 +10,12 @@ import android.os.Parcelable
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.hilt.navigation.compose.hiltViewModel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import ua.acclorite.book_story.presentation.browse.BrowseScreen import ua.acclorite.book_story.presentation.browse.BrowseScreen
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.navigator.Screen import ua.acclorite.book_story.presentation.navigator.Screen
import ua.acclorite.book_story.presentation.start.StartScreen import ua.acclorite.book_story.presentation.start.StartScreen
import ua.acclorite.book_story.ui.common.components.top_bar.collapsibleTopAppBarScrollBehavior import ua.acclorite.book_story.ui.common.components.top_bar.collapsibleTopAppBarScrollBehavior
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.help.HelpContent import ua.acclorite.book_story.ui.help.HelpContent
import ua.acclorite.book_story.ui.navigator.LocalNavigator import ua.acclorite.book_story.ui.navigator.LocalNavigator
@ -28,7 +26,7 @@ data class HelpScreen(val fromStart: Boolean) : Screen, Parcelable {
@Composable @Composable
override fun Content() { override fun Content() {
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val (scrollBehavior, listState) = TopAppBarDefaults.collapsibleTopAppBarScrollBehavior() val (scrollBehavior, listState) = TopAppBarDefaults.collapsibleTopAppBarScrollBehavior()
@ -36,12 +34,12 @@ data class HelpScreen(val fromStart: Boolean) : Screen, Parcelable {
fromStart = fromStart, fromStart = fromStart,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
listState = listState, listState = listState,
changeShowStartScreen = mainModel::onEvent, changeShowStartScreen = { settings.showStartScreen.update(it) },
navigateToBrowse = { navigateToBrowse = {
navigator.push(BrowseScreen, saveInBackStack = false) navigator.push(BrowseScreen, saveInBackStack = false)
}, },
navigateToStart = { navigateToStart = {
mainModel.onEvent(MainEvent.OnChangeShowStartScreen(true)) settings.showStartScreen.update(true)
navigator.push(StartScreen, saveInBackStack = false) navigator.push(StartScreen, saveInBackStack = false)
}, },
navigateBack = { navigateBack = {

View file

@ -26,11 +26,11 @@ import kotlinx.parcelize.Parcelize
import ua.acclorite.book_story.presentation.book_info.BookInfoScreen import ua.acclorite.book_story.presentation.book_info.BookInfoScreen
import ua.acclorite.book_story.presentation.browse.BrowseScreen import ua.acclorite.book_story.presentation.browse.BrowseScreen
import ua.acclorite.book_story.presentation.history.HistoryScreen import ua.acclorite.book_story.presentation.history.HistoryScreen
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.navigator.Screen import ua.acclorite.book_story.presentation.navigator.Screen
import ua.acclorite.book_story.presentation.reader.ReaderScreen import ua.acclorite.book_story.presentation.reader.ReaderScreen
import ua.acclorite.book_story.presentation.settings.LibrarySettingsScreen import ua.acclorite.book_story.presentation.settings.LibrarySettingsScreen
import ua.acclorite.book_story.presentation.settings.SettingsModel import ua.acclorite.book_story.presentation.settings.SettingsModel
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.library.LibraryContent import ua.acclorite.book_story.ui.library.LibraryContent
import ua.acclorite.book_story.ui.navigator.LocalNavigator import ua.acclorite.book_story.ui.navigator.LocalNavigator
@ -59,22 +59,23 @@ object LibraryScreen : Screen, Parcelable {
@Composable @Composable
override fun Content() { override fun Content() {
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val screenModel = hiltViewModel<LibraryModel>() val screenModel = hiltViewModel<LibraryModel>()
val mainModel = hiltViewModel<MainModel>()
val settingsModel = hiltViewModel<SettingsModel>() val settingsModel = hiltViewModel<SettingsModel>()
val settings = LocalSettings.current
val state = screenModel.state.collectAsStateWithLifecycle() val state = screenModel.state.collectAsStateWithLifecycle()
val mainState = mainModel.state.collectAsStateWithLifecycle()
val settingsState = settingsModel.state.collectAsStateWithLifecycle() val settingsState = settingsModel.state.collectAsStateWithLifecycle()
val showDefaultCategory = remember(state.value.books, settingsState.value.categories) { val showDefaultCategory = remember(
state.value.books,
settingsState.value.categories,
settings.libraryShowDefaultTab.value
) {
derivedStateOf { derivedStateOf {
val categoryIds = settingsState.value.categories.map { it.id }.toSet() val categoryIds = settingsState.value.categories.map { it.id }.toSet()
state.value.books.any { book -> state.value.books.any { book ->
book.data.categories.none { category -> category in categoryIds } book.data.categories.none { category -> category in categoryIds }
} || settingsState.value.categories.isEmpty() } || settingsState.value.categories.isEmpty() || settings.libraryShowDefaultTab.lastValue
|| mainState.value.libraryAlwaysShowDefaultTab
} }
} }
@ -108,11 +109,11 @@ object LibraryScreen : Screen, Parcelable {
books = state.value.books, books = state.value.books,
selectedItemsCount = state.value.selectedItemsCount, selectedItemsCount = state.value.selectedItemsCount,
hasSelectedItems = state.value.hasSelectedItems, hasSelectedItems = state.value.hasSelectedItems,
titlePosition = mainState.value.libraryTitlePosition, titlePosition = settings.libraryTitlePosition.value,
readButton = mainState.value.libraryReadButton, readButton = settings.libraryShowReadButton.value,
showProgress = mainState.value.libraryShowProgress, showProgress = settings.libraryShowProgress.value,
showCategoryTabs = mainState.value.libraryShowCategoryTabs, showCategoryTabs = settings.libraryShowCategoryTabs.value,
showBookCount = mainState.value.libraryShowBookCount, showBookCount = settings.libraryShowBookCount.value,
showSearch = state.value.showSearch, showSearch = state.value.showSearch,
searchQuery = state.value.searchQuery, searchQuery = state.value.searchQuery,
bookCount = state.value.books.count(), bookCount = state.value.books.count(),
@ -120,22 +121,22 @@ object LibraryScreen : Screen, Parcelable {
pagerState = pagerState, pagerState = pagerState,
isLoading = state.value.isLoading, isLoading = state.value.isLoading,
isRefreshing = state.value.isRefreshing, isRefreshing = state.value.isRefreshing,
doublePressExit = mainState.value.doublePressExit, doublePressExit = settings.doublePressExit.value,
layout = mainState.value.libraryLayout, layout = settings.libraryLayout.value,
gridSize = mainState.value.libraryGridSize, gridSize = settings.libraryGridSize.value,
autoGridSize = mainState.value.libraryAutoGridSize, autoGridSize = settings.libraryAutoGridSize.value,
categories = settingsState.value.categories, categories = settingsState.value.categories,
showDefaultCategory = showDefaultCategory.value, showDefaultCategory = showDefaultCategory.value,
categoriesSort = settingsState.value.categoriesSort, categoriesSort = settingsState.value.categoriesSort,
sortOrder = mainState.value.librarySortOrder, sortOrder = settings.librarySortOrder.value,
sortOrderDescending = mainState.value.librarySortOrderDescending, sortOrderDescending = settings.librarySortOrderDescending.value,
perCategorySort = mainState.value.libraryPerCategorySort, perCategorySort = settings.libraryPerCategorySort.value,
refreshState = refreshState, refreshState = refreshState,
dialog = state.value.dialog, dialog = state.value.dialog,
bottomSheet = state.value.bottomSheet, bottomSheet = state.value.bottomSheet,
updateCategorySort = settingsModel::onEvent, updateCategorySort = settingsModel::onEvent,
changeLibrarySortOrder = mainModel::onEvent, changeSortOrder = { settings.librarySortOrder.update(it) },
changeLibrarySortOrderDescending = mainModel::onEvent, changeSortOrderDescending = { settings.librarySortOrderDescending.update(it) },
selectBook = screenModel::onEvent, selectBook = screenModel::onEvent,
searchVisibility = screenModel::onEvent, searchVisibility = screenModel::onEvent,
requestFocus = screenModel::onEvent, requestFocus = screenModel::onEvent,

View file

@ -23,44 +23,42 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.settings.SettingsManager
import ua.acclorite.book_story.presentation.browse.BrowseModel import ua.acclorite.book_story.presentation.browse.BrowseModel
import ua.acclorite.book_story.presentation.browse.BrowseScreen import ua.acclorite.book_story.presentation.browse.BrowseScreen
import ua.acclorite.book_story.presentation.history.HistoryModel import ua.acclorite.book_story.presentation.history.HistoryModel
import ua.acclorite.book_story.presentation.history.HistoryScreen import ua.acclorite.book_story.presentation.history.HistoryScreen
import ua.acclorite.book_story.presentation.library.LibraryModel import ua.acclorite.book_story.presentation.library.LibraryModel
import ua.acclorite.book_story.presentation.library.LibraryScreen import ua.acclorite.book_story.presentation.library.LibraryScreen
import ua.acclorite.book_story.presentation.main.model.isDark
import ua.acclorite.book_story.presentation.main.model.isPureDark
import ua.acclorite.book_story.presentation.navigator.NavigatorItem import ua.acclorite.book_story.presentation.navigator.NavigatorItem
import ua.acclorite.book_story.presentation.navigator.StackEvent import ua.acclorite.book_story.presentation.navigator.StackEvent
import ua.acclorite.book_story.presentation.settings.SettingsModel import ua.acclorite.book_story.presentation.settings.SettingsModel
import ua.acclorite.book_story.presentation.start.StartScreen import ua.acclorite.book_story.presentation.start.StartScreen
import ua.acclorite.book_story.ui.common.components.navigation_bar.NavigationBar import ua.acclorite.book_story.ui.common.components.navigation_bar.NavigationBar
import ua.acclorite.book_story.ui.common.components.navigation_rail.NavigationRail import ua.acclorite.book_story.ui.common.components.navigation_rail.NavigationRail
import ua.acclorite.book_story.ui.common.helpers.ProvideSettings
import ua.acclorite.book_story.ui.main.MainActivityKeyboardManager import ua.acclorite.book_story.ui.main.MainActivityKeyboardManager
import ua.acclorite.book_story.ui.navigator.Navigator import ua.acclorite.book_story.ui.navigator.Navigator
import ua.acclorite.book_story.ui.navigator.NavigatorTabs import ua.acclorite.book_story.ui.navigator.NavigatorTabs
import ua.acclorite.book_story.ui.theme.BookStoryTheme import ua.acclorite.book_story.ui.theme.BookStoryTheme
import ua.acclorite.book_story.ui.theme.Transitions import ua.acclorite.book_story.ui.theme.Transitions
import java.lang.reflect.Field import java.lang.reflect.Field
import javax.inject.Inject
@SuppressLint("DiscouragedPrivateApi") @SuppressLint("DiscouragedPrivateApi")
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
// Creating an instance of Models
private val mainModel: MainModel by viewModels() @Inject
lateinit var settings: SettingsManager
private val settingsModel: SettingsModel by viewModels() private val settingsModel: SettingsModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// Splash screen installSplashScreen().setKeepOnScreenCondition {
installSplashScreen().apply { !settings.initialized.value || !settingsModel.isReady.value
setKeepOnScreenCondition {
!mainModel.isReady.value
}
} }
// Default super
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// Bigger Cursor size for Room // Bigger Cursor size for Room
@ -72,9 +70,6 @@ class MainActivity : AppCompatActivity() {
e.printStackTrace() e.printStackTrace()
} }
// Initializing the MainModel
mainModel.init(settingsModel.isReady)
// Edge to edge // Edge to edge
enableEdgeToEdge() enableEdgeToEdge()
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
@ -85,84 +80,83 @@ class MainActivity : AppCompatActivity() {
val historyModel = hiltViewModel<HistoryModel>() val historyModel = hiltViewModel<HistoryModel>()
val browseModel = hiltViewModel<BrowseModel>() val browseModel = hiltViewModel<BrowseModel>()
val state = mainModel.state.collectAsStateWithLifecycle() ProvideSettings(settings) {
val isLoaded = mainModel.isReady.collectAsStateWithLifecycle() val tabs = persistentListOf(
NavigatorItem(
val tabs = persistentListOf( screen = LibraryScreen,
NavigatorItem( title = R.string.library_screen,
screen = LibraryScreen, tooltip = R.string.library_content_desc,
title = R.string.library_screen, selectedIcon = R.drawable.library_screen_filled,
tooltip = R.string.library_content_desc, unselectedIcon = R.drawable.library_screen_outlined
selectedIcon = R.drawable.library_screen_filled, ),
unselectedIcon = R.drawable.library_screen_outlined NavigatorItem(
), screen = HistoryScreen,
NavigatorItem( title = R.string.history_screen,
screen = HistoryScreen, tooltip = R.string.history_content_desc,
title = R.string.history_screen, selectedIcon = R.drawable.history_screen_filled,
tooltip = R.string.history_content_desc, unselectedIcon = R.drawable.history_screen_outlined
selectedIcon = R.drawable.history_screen_filled, ),
unselectedIcon = R.drawable.history_screen_outlined NavigatorItem(
), screen = BrowseScreen,
NavigatorItem( title = R.string.browse_screen,
screen = BrowseScreen, tooltip = R.string.browse_content_desc,
title = R.string.browse_screen, selectedIcon = R.drawable.browse_screen_filled,
tooltip = R.string.browse_content_desc, unselectedIcon = R.drawable.browse_screen_outlined
selectedIcon = R.drawable.browse_screen_filled, )
unselectedIcon = R.drawable.browse_screen_outlined
) )
)
MainActivityKeyboardManager() MainActivityKeyboardManager()
if (isLoaded.value) { if (settings.initialized.collectAsStateWithLifecycle().value) {
BookStoryTheme( BookStoryTheme(
theme = state.value.theme, theme = settings.theme.value,
isDark = state.value.darkTheme.isDark(), isDark = settings.darkTheme.value.isDark(),
isPureDark = state.value.pureDark.isPureDark(this), isPureDark = settings.pureDark.value.isPureDark(this),
themeContrast = state.value.themeContrast themeContrast = settings.themeContrast.value
) { ) {
Navigator( Navigator(
initialScreen = if (state.value.showStartScreen) StartScreen initialScreen = if (settings.showStartScreen.value) StartScreen
else LibraryScreen, else LibraryScreen,
transitionSpec = { lastEvent -> transitionSpec = { lastEvent ->
when (lastEvent) { when (lastEvent) {
StackEvent.Default -> { StackEvent.Default -> {
Transitions.SlidingTransitionIn Transitions.SlidingTransitionIn
.togetherWith(Transitions.SlidingTransitionOut) .togetherWith(Transitions.SlidingTransitionOut)
}
StackEvent.Pop -> {
Transitions.BackSlidingTransitionIn
.togetherWith(Transitions.BackSlidingTransitionOut)
}
}
},
contentKey = {
when (it) {
LibraryScreen, HistoryScreen, BrowseScreen -> "tabs"
else -> it
}
},
backHandlerEnabled = { it != StartScreen }
) { screen ->
when (screen) {
LibraryScreen, HistoryScreen, BrowseScreen -> {
NavigatorTabs(
currentTab = screen,
transitionSpec = {
Transitions.FadeTransitionIn
.togetherWith(Transitions.FadeTransitionOut)
},
navigationBar = { NavigationBar(tabs = tabs) },
navigationRail = { NavigationRail(tabs = tabs) }
) { tab ->
tab.Content()
}
} }
StackEvent.Pop -> { else -> {
Transitions.BackSlidingTransitionIn screen.Content()
.togetherWith(Transitions.BackSlidingTransitionOut)
} }
} }
},
contentKey = {
when (it) {
LibraryScreen, HistoryScreen, BrowseScreen -> "tabs"
else -> it
}
},
backHandlerEnabled = { it != StartScreen }
) { screen ->
when (screen) {
LibraryScreen, HistoryScreen, BrowseScreen -> {
NavigatorTabs(
currentTab = screen,
transitionSpec = {
Transitions.FadeTransitionIn
.togetherWith(Transitions.FadeTransitionOut)
},
navigationBar = { NavigationBar(tabs = tabs) },
navigationRail = { NavigationRail(tabs = tabs) }
) { tab ->
tab.Content()
}
}
else -> {
screen.Content()
}
} }
} }
} }

View file

@ -1,83 +0,0 @@
/*
* 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.main
import androidx.compose.runtime.Immutable
@Immutable
sealed class MainEvent {
data class OnChangeLanguage(val value: String) : MainEvent()
data class OnChangeTheme(val value: String) : MainEvent()
data class OnChangeDarkTheme(val value: String) : MainEvent()
data class OnChangePureDark(val value: String) : MainEvent()
data class OnChangeThemeContrast(val value: String) : MainEvent()
data class OnChangeFontFamily(val value: String) : MainEvent()
data class OnChangeFontStyle(val value: Boolean) : MainEvent()
data class OnChangeFontSize(val value: Int) : MainEvent()
data class OnChangeLineHeight(val value: Int) : MainEvent()
data class OnChangeParagraphHeight(val value: Int) : MainEvent()
data class OnChangeParagraphIndentation(val value: Int) : MainEvent()
data class OnChangeShowStartScreen(val value: Boolean) : MainEvent()
data class OnChangeSidePadding(val value: Int) : MainEvent()
data class OnChangeDoubleClickTranslation(val value: Boolean) : MainEvent()
data class OnChangeFastColorPresetChange(val value: Boolean) : MainEvent()
data class OnChangeBrowseLayout(val value: String) : MainEvent()
data class OnChangeBrowseAutoGridSize(val value: Boolean) : MainEvent()
data class OnChangeBrowseGridSize(val value: Int) : MainEvent()
data class OnChangeBrowseSortOrder(val value: String) : MainEvent()
data class OnChangeBrowseSortOrderDescending(val value: Boolean) : MainEvent()
data class OnChangeBrowseIncludedFilterItem(val value: String) : MainEvent()
data class OnChangeTextAlignment(val value: String) : MainEvent()
data class OnChangeDoublePressExit(val value: Boolean) : MainEvent()
data class OnChangeLetterSpacing(val value: Int) : MainEvent()
data class OnChangeAbsoluteDark(val value: Boolean) : MainEvent()
data class OnChangeCutoutPadding(val value: Boolean) : MainEvent()
data class OnChangeFullscreen(val value: Boolean) : MainEvent()
data class OnChangeKeepScreenOn(val value: Boolean) : MainEvent()
data class OnChangeVerticalPadding(val value: Int) : MainEvent()
data class OnChangeHideBarsOnFastScroll(val value: Boolean) : MainEvent()
data class OnChangePerceptionExpander(val value: Boolean) : MainEvent()
data class OnChangePerceptionExpanderPadding(val value: Int) : MainEvent()
data class OnChangePerceptionExpanderThickness(val value: Int) : MainEvent()
data class OnChangeScreenOrientation(val value: String) : MainEvent()
data class OnChangeCustomScreenBrightness(val value: Boolean) : MainEvent()
data class OnChangeScreenBrightness(val value: Float) : MainEvent()
data class OnChangeHorizontalGesture(val value: String) : MainEvent()
data class OnChangeHorizontalGestureScroll(val value: Float) : MainEvent()
data class OnChangeHorizontalGestureSensitivity(val value: Float) : MainEvent()
data class OnChangeBottomBarPadding(val value: Int) : MainEvent()
data class OnChangeHighlightedReading(val value: Boolean) : MainEvent()
data class OnChangeHighlightedReadingThickness(val value: Int) : MainEvent()
data class OnChangeChapterTitleAlignment(val value: String) : MainEvent()
data class OnChangeImages(val value: Boolean) : MainEvent()
data class OnChangeImagesCaptions(val value: Boolean) : MainEvent()
data class OnChangeImagesCornersRoundness(val value: Int) : MainEvent()
data class OnChangeImagesAlignment(val value: String) : MainEvent()
data class OnChangeImagesWidth(val value: Float) : MainEvent()
data class OnChangeImagesColorEffects(val value: String) : MainEvent()
data class OnChangeProgressBar(val value: Boolean) : MainEvent()
data class OnChangeProgressBarPadding(val value: Int) : MainEvent()
data class OnChangeProgressBarAlignment(val value: String) : MainEvent()
data class OnChangeProgressBarFontSize(val value: Int) : MainEvent()
data class OnChangeBrowsePinnedPaths(val value: String) : MainEvent()
data class OnChangeFontThickness(val value: String) : MainEvent()
data class OnChangeProgressCount(val value: String) : MainEvent()
data class OnChangeHorizontalGestureAlphaAnim(val value: Boolean) : MainEvent()
data class OnChangeHorizontalGesturePullAnim(val value: Boolean) : MainEvent()
data class OnChangeLibraryLayout(val value: String) : MainEvent()
data class OnChangeLibraryAutoGridSize(val value: Boolean) : MainEvent()
data class OnChangeLibraryGridSize(val value: Int) : MainEvent()
data class OnChangeLibraryReadButton(val value: Boolean) : MainEvent()
data class OnChangeLibraryShowProgress(val value: Boolean) : MainEvent()
data class OnChangeLibraryTitlePosition(val value: String) : MainEvent()
data class OnChangeLibraryShowBookCount(val value: Boolean) : MainEvent()
data class OnChangeLibraryShowCategoryTabs(val value: Boolean) : MainEvent()
data class OnChangeLibraryAlwaysShowDefaultTab(val value: Boolean) : MainEvent()
data class OnChangeLibrarySortOrder(val value: String) : MainEvent()
data class OnChangeLibrarySortOrderDescending(val value: Boolean) : MainEvent()
data class OnChangeLibraryPerCategorySort(val value: Boolean) : MainEvent()
}

View file

@ -1,732 +0,0 @@
/*
* 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.main
import androidx.datastore.preferences.core.Preferences
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
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.domain.use_case.data_store.ChangeLanguagePreferenceUseCase
import ua.acclorite.book_story.domain.use_case.data_store.GetPreferencesUseCase
import ua.acclorite.book_story.domain.use_case.data_store.PutPreferenceUseCase
import ua.acclorite.book_story.presentation.browse.model.BrowseLayout
import ua.acclorite.book_story.presentation.browse.model.BrowseSortOrder
import ua.acclorite.book_story.presentation.library.model.LibraryLayout
import ua.acclorite.book_story.presentation.library.model.LibrarySortOrder
import ua.acclorite.book_story.presentation.library.model.LibraryTitlePosition
import ua.acclorite.book_story.presentation.main.data.DataStoreData
import ua.acclorite.book_story.presentation.main.model.DarkTheme
import ua.acclorite.book_story.presentation.main.model.HorizontalAlignment
import ua.acclorite.book_story.presentation.main.model.PureDark
import ua.acclorite.book_story.presentation.main.model.ThemeContrast
import ua.acclorite.book_story.presentation.reader.model.ReaderColorEffects
import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness
import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture
import ua.acclorite.book_story.presentation.reader.model.ReaderProgressCount
import ua.acclorite.book_story.presentation.reader.model.ReaderScreenOrientation
import ua.acclorite.book_story.presentation.reader.model.ReaderTextAlignment
import ua.acclorite.book_story.ui.reader.data.ReaderData
import ua.acclorite.book_story.ui.theme.Theme
import javax.inject.Inject
@HiltViewModel
class MainModel @Inject constructor(
private val stateHandle: SavedStateHandle,
private val putPreferenceUseCase: PutPreferenceUseCase,
private val changeLanguagePreferenceUseCase: ChangeLanguagePreferenceUseCase,
private val getPreferencesUseCase: GetPreferencesUseCase
) : ViewModel() {
private val mutex = Mutex()
private val _isReady = MutableStateFlow(false)
val isReady = _isReady.asStateFlow()
private val mainModelReady = MutableStateFlow(false)
private val _state: MutableStateFlow<MainState> = MutableStateFlow(
stateHandle["main_state"] ?: MainState()
)
val state = _state.asStateFlow()
fun onEvent(event: MainEvent) {
when (event) {
is MainEvent.OnChangeLanguage -> handleLanguageUpdate(event)
is MainEvent.OnChangeDarkTheme -> handleDatastoreUpdate(
key = DataStoreData.DARK_THEME,
value = event.value,
updateState = {
it.copy(darkTheme = DarkTheme.valueOf(this))
}
)
is MainEvent.OnChangePureDark -> handleDatastoreUpdate(
key = DataStoreData.PURE_DARK,
value = event.value,
updateState = {
it.copy(pureDark = PureDark.valueOf(this))
}
)
is MainEvent.OnChangeThemeContrast -> handleDatastoreUpdate(
key = DataStoreData.THEME_CONTRAST,
value = event.value,
updateState = {
it.copy(themeContrast = ThemeContrast.valueOf(this))
}
)
is MainEvent.OnChangeTheme -> handleDatastoreUpdate(
key = DataStoreData.THEME,
value = event.value,
updateState = {
it.copy(theme = Theme.valueOf(this))
}
)
is MainEvent.OnChangeFontFamily -> handleDatastoreUpdate(
key = DataStoreData.FONT,
value = event.value,
updateState = {
it.copy(
fontFamily = ReaderData.fonts.run {
find { font ->
font.id == event.value
}?.id ?: get(0).id
}
)
}
)
is MainEvent.OnChangeFontStyle -> handleDatastoreUpdate(
key = DataStoreData.IS_ITALIC,
value = event.value,
updateState = {
it.copy(isItalic = this)
}
)
is MainEvent.OnChangeFontSize -> handleDatastoreUpdate(
key = DataStoreData.FONT_SIZE,
value = event.value,
updateState = {
it.copy(fontSize = this)
}
)
is MainEvent.OnChangeLineHeight -> handleDatastoreUpdate(
key = DataStoreData.LINE_HEIGHT,
value = event.value,
updateState = {
it.copy(lineHeight = this)
}
)
is MainEvent.OnChangeParagraphHeight -> handleDatastoreUpdate(
key = DataStoreData.PARAGRAPH_HEIGHT,
value = event.value,
updateState = {
it.copy(paragraphHeight = this)
}
)
is MainEvent.OnChangeParagraphIndentation -> handleDatastoreUpdate(
key = DataStoreData.PARAGRAPH_INDENTATION,
value = event.value,
updateState = {
it.copy(paragraphIndentation = this)
}
)
is MainEvent.OnChangeShowStartScreen -> handleDatastoreUpdate(
key = DataStoreData.SHOW_START_SCREEN,
value = event.value,
updateState = {
it.copy(showStartScreen = this)
}
)
is MainEvent.OnChangeSidePadding -> handleDatastoreUpdate(
key = DataStoreData.SIDE_PADDING,
value = event.value,
updateState = {
it.copy(sidePadding = this)
}
)
is MainEvent.OnChangeDoubleClickTranslation -> handleDatastoreUpdate(
key = DataStoreData.DOUBLE_CLICK_TRANSLATION,
value = event.value,
updateState = {
it.copy(doubleClickTranslation = this)
}
)
is MainEvent.OnChangeFastColorPresetChange -> handleDatastoreUpdate(
key = DataStoreData.FAST_COLOR_PRESET_CHANGE,
value = event.value,
updateState = {
it.copy(fastColorPresetChange = this)
}
)
is MainEvent.OnChangeBrowseLayout -> handleDatastoreUpdate(
key = DataStoreData.BROWSE_LAYOUT,
value = event.value,
updateState = {
it.copy(browseLayout = BrowseLayout.valueOf(this))
}
)
is MainEvent.OnChangeBrowseAutoGridSize -> handleDatastoreUpdate(
key = DataStoreData.BROWSE_AUTO_GRID_SIZE,
value = event.value,
updateState = {
it.copy(browseAutoGridSize = this)
}
)
is MainEvent.OnChangeBrowseGridSize -> handleDatastoreUpdate(
key = DataStoreData.BROWSE_GRID_SIZE,
value = event.value,
updateState = {
it.copy(browseGridSize = this)
}
)
is MainEvent.OnChangeBrowseSortOrder -> handleDatastoreUpdate(
key = DataStoreData.BROWSE_SORT_ORDER,
value = event.value,
updateState = {
it.copy(browseSortOrder = BrowseSortOrder.valueOf(this))
}
)
is MainEvent.OnChangeBrowseSortOrderDescending -> handleDatastoreUpdate(
key = DataStoreData.BROWSE_SORT_ORDER_DESCENDING,
value = event.value,
updateState = {
it.copy(browseSortOrderDescending = this)
}
)
is MainEvent.OnChangeBrowseIncludedFilterItem -> handleBrowseIncludedFilterItemUpdate(
event = event
)
is MainEvent.OnChangeTextAlignment -> handleDatastoreUpdate(
key = DataStoreData.TEXT_ALIGNMENT,
value = event.value,
updateState = {
it.copy(textAlignment = ReaderTextAlignment.valueOf(this))
}
)
is MainEvent.OnChangeDoublePressExit -> handleDatastoreUpdate(
key = DataStoreData.DOUBLE_PRESS_EXIT,
value = event.value,
updateState = {
it.copy(doublePressExit = this)
}
)
is MainEvent.OnChangeLetterSpacing -> handleDatastoreUpdate(
key = DataStoreData.LETTER_SPACING,
value = event.value,
updateState = {
it.copy(letterSpacing = this)
}
)
is MainEvent.OnChangeAbsoluteDark -> handleDatastoreUpdate(
key = DataStoreData.ABSOLUTE_DARK,
value = event.value,
updateState = {
it.copy(absoluteDark = this)
}
)
is MainEvent.OnChangeCutoutPadding -> handleDatastoreUpdate(
key = DataStoreData.CUTOUT_PADDING,
value = event.value,
updateState = {
it.copy(cutoutPadding = this)
}
)
is MainEvent.OnChangeFullscreen -> handleDatastoreUpdate(
key = DataStoreData.FULLSCREEN,
value = event.value,
updateState = {
it.copy(fullscreen = this)
}
)
is MainEvent.OnChangeKeepScreenOn -> handleDatastoreUpdate(
key = DataStoreData.KEEP_SCREEN_ON,
value = event.value,
updateState = {
it.copy(keepScreenOn = this)
}
)
is MainEvent.OnChangeVerticalPadding -> handleDatastoreUpdate(
key = DataStoreData.VERTICAL_PADDING,
value = event.value,
updateState = {
it.copy(verticalPadding = this)
}
)
is MainEvent.OnChangeHideBarsOnFastScroll -> handleDatastoreUpdate(
key = DataStoreData.HIDE_BARS_ON_FAST_SCROLL,
value = event.value,
updateState = {
it.copy(hideBarsOnFastScroll = this)
}
)
is MainEvent.OnChangePerceptionExpander -> handleDatastoreUpdate(
key = DataStoreData.PERCEPTION_EXPANDER,
value = event.value,
updateState = {
it.copy(perceptionExpander = this)
}
)
is MainEvent.OnChangePerceptionExpanderPadding -> handleDatastoreUpdate(
key = DataStoreData.PERCEPTION_EXPANDER_PADDING,
value = event.value,
updateState = {
it.copy(perceptionExpanderPadding = this)
}
)
is MainEvent.OnChangePerceptionExpanderThickness -> handleDatastoreUpdate(
key = DataStoreData.PERCEPTION_EXPANDER_THICKNESS,
value = event.value,
updateState = {
it.copy(perceptionExpanderThickness = this)
}
)
is MainEvent.OnChangeScreenOrientation -> handleDatastoreUpdate(
key = DataStoreData.SCREEN_ORIENTATION,
value = event.value,
updateState = {
it.copy(screenOrientation = ReaderScreenOrientation.valueOf(this))
}
)
is MainEvent.OnChangeCustomScreenBrightness -> handleDatastoreUpdate(
key = DataStoreData.CUSTOM_SCREEN_BRIGHTNESS,
value = event.value,
updateState = {
it.copy(customScreenBrightness = this)
}
)
is MainEvent.OnChangeScreenBrightness -> handleDatastoreUpdate(
key = DataStoreData.SCREEN_BRIGHTNESS,
value = event.value.toDouble(),
updateState = {
it.copy(screenBrightness = this.toFloat())
}
)
is MainEvent.OnChangeHorizontalGesture -> handleDatastoreUpdate(
key = DataStoreData.HORIZONTAL_GESTURE,
value = event.value,
updateState = {
it.copy(horizontalGesture = ReaderHorizontalGesture.valueOf(this))
}
)
is MainEvent.OnChangeHorizontalGestureScroll -> handleDatastoreUpdate(
key = DataStoreData.HORIZONTAL_GESTURE_SCROLL,
value = event.value.toDouble(),
updateState = {
it.copy(horizontalGestureScroll = this.toFloat())
}
)
is MainEvent.OnChangeHorizontalGestureSensitivity -> handleDatastoreUpdate(
key = DataStoreData.HORIZONTAL_GESTURE_SENSITIVITY,
value = event.value.toDouble(),
updateState = {
it.copy(horizontalGestureSensitivity = this.toFloat())
}
)
is MainEvent.OnChangeBottomBarPadding -> handleDatastoreUpdate(
key = DataStoreData.BOTTOM_BAR_PADDING,
value = event.value,
updateState = {
it.copy(bottomBarPadding = this)
}
)
is MainEvent.OnChangeHighlightedReading -> handleDatastoreUpdate(
key = DataStoreData.HIGHLIGHTED_READING,
value = event.value,
updateState = {
it.copy(highlightedReading = this)
}
)
is MainEvent.OnChangeHighlightedReadingThickness -> handleDatastoreUpdate(
key = DataStoreData.HIGHLIGHTED_READING_THICKNESS,
value = event.value,
updateState = {
it.copy(highlightedReadingThickness = this)
}
)
is MainEvent.OnChangeChapterTitleAlignment -> handleDatastoreUpdate(
key = DataStoreData.CHAPTER_TITLE_ALIGNMENT,
value = event.value,
updateState = {
it.copy(chapterTitleAlignment = ReaderTextAlignment.valueOf(this))
}
)
is MainEvent.OnChangeImages -> handleDatastoreUpdate(
key = DataStoreData.IMAGES,
value = event.value,
updateState = {
it.copy(images = this)
}
)
is MainEvent.OnChangeImagesCaptions -> handleDatastoreUpdate(
key = DataStoreData.IMAGES_CAPTIONS,
value = event.value,
updateState = {
it.copy(imagesCaptions = this)
}
)
is MainEvent.OnChangeImagesCornersRoundness -> handleDatastoreUpdate(
key = DataStoreData.IMAGES_CORNERS_ROUNDNESS,
value = event.value,
updateState = {
it.copy(imagesCornersRoundness = this)
}
)
is MainEvent.OnChangeImagesAlignment -> handleDatastoreUpdate(
key = DataStoreData.IMAGES_ALIGNMENT,
value = event.value,
updateState = {
it.copy(imagesAlignment = HorizontalAlignment.valueOf(this))
}
)
is MainEvent.OnChangeImagesWidth -> handleDatastoreUpdate(
key = DataStoreData.IMAGES_WIDTH,
value = event.value.toDouble(),
updateState = {
it.copy(imagesWidth = this.toFloat())
}
)
is MainEvent.OnChangeImagesColorEffects -> handleDatastoreUpdate(
key = DataStoreData.IMAGES_COLOR_EFFECTS,
value = event.value,
updateState = {
it.copy(imagesColorEffects = ReaderColorEffects.valueOf(this))
}
)
is MainEvent.OnChangeProgressBar -> handleDatastoreUpdate(
key = DataStoreData.PROGRESS_BAR,
value = event.value,
updateState = {
it.copy(progressBar = this)
}
)
is MainEvent.OnChangeProgressBarPadding -> handleDatastoreUpdate(
key = DataStoreData.PROGRESS_BAR_PADDING,
value = event.value,
updateState = {
it.copy(progressBarPadding = this)
}
)
is MainEvent.OnChangeProgressBarAlignment -> handleDatastoreUpdate(
key = DataStoreData.PROGRESS_BAR_ALIGNMENT,
value = event.value,
updateState = {
it.copy(progressBarAlignment = HorizontalAlignment.valueOf(this))
}
)
is MainEvent.OnChangeProgressBarFontSize -> handleDatastoreUpdate(
key = DataStoreData.PROGRESS_BAR_FONT_SIZE,
value = event.value,
updateState = {
it.copy(progressBarFontSize = this)
}
)
is MainEvent.OnChangeBrowsePinnedPaths -> handleBrowsePinnedPathsUpdate(
event = event
)
is MainEvent.OnChangeFontThickness -> handleDatastoreUpdate(
key = DataStoreData.FONT_THICKNESS,
value = event.value,
updateState = {
it.copy(fontThickness = ReaderFontThickness.valueOf(this))
}
)
is MainEvent.OnChangeProgressCount -> handleDatastoreUpdate(
key = DataStoreData.PROGRESS_COUNT,
value = event.value,
updateState = {
it.copy(progressCount = ReaderProgressCount.valueOf(this))
}
)
is MainEvent.OnChangeHorizontalGestureAlphaAnim -> handleDatastoreUpdate(
key = DataStoreData.HORIZONTAL_GESTURE_ALPHA_ANIM,
value = event.value,
updateState = {
it.copy(horizontalGestureAlphaAnim = this)
}
)
is MainEvent.OnChangeHorizontalGesturePullAnim -> handleDatastoreUpdate(
key = DataStoreData.HORIZONTAL_GESTURE_PULL_ANIM,
value = event.value,
updateState = {
it.copy(horizontalGesturePullAnim = this)
}
)
is MainEvent.OnChangeLibraryLayout -> handleDatastoreUpdate(
key = DataStoreData.LIBRARY_LAYOUT,
value = event.value,
updateState = {
it.copy(libraryLayout = LibraryLayout.valueOf(this))
}
)
is MainEvent.OnChangeLibraryAutoGridSize -> handleDatastoreUpdate(
key = DataStoreData.LIBRARY_AUTO_GRID_SIZE,
value = event.value,
updateState = {
it.copy(libraryAutoGridSize = this)
}
)
is MainEvent.OnChangeLibraryGridSize -> handleDatastoreUpdate(
key = DataStoreData.LIBRARY_GRID_SIZE,
value = event.value,
updateState = {
it.copy(libraryGridSize = this)
}
)
is MainEvent.OnChangeLibraryReadButton -> handleDatastoreUpdate(
key = DataStoreData.LIBRARY_READ_BUTTON,
value = event.value,
updateState = {
it.copy(libraryReadButton = this)
}
)
is MainEvent.OnChangeLibraryShowProgress -> handleDatastoreUpdate(
key = DataStoreData.LIBRARY_SHOW_PROGRESS,
value = event.value,
updateState = {
it.copy(libraryShowProgress = this)
}
)
is MainEvent.OnChangeLibraryTitlePosition -> handleDatastoreUpdate(
key = DataStoreData.LIBRARY_TITLE_POSITION,
value = event.value,
updateState = {
it.copy(libraryTitlePosition = LibraryTitlePosition.valueOf(this))
}
)
is MainEvent.OnChangeLibraryShowBookCount -> handleDatastoreUpdate(
key = DataStoreData.LIBRARY_SHOW_BOOK_COUNT,
value = event.value,
updateState = {
it.copy(libraryShowBookCount = this)
}
)
is MainEvent.OnChangeLibraryShowCategoryTabs -> handleDatastoreUpdate(
key = DataStoreData.LIBRARY_SHOW_CATEGORY_TABS,
value = event.value,
updateState = {
it.copy(libraryShowCategoryTabs = this)
}
)
is MainEvent.OnChangeLibraryAlwaysShowDefaultTab -> handleDatastoreUpdate(
key = DataStoreData.LIBRARY_ALWAYS_SHOW_DEFAULT_TAB,
value = event.value,
updateState = {
it.copy(libraryAlwaysShowDefaultTab = this)
}
)
is MainEvent.OnChangeLibrarySortOrder -> handleDatastoreUpdate(
key = DataStoreData.LIBRARY_SORT_ORDER,
value = event.value,
updateState = {
it.copy(librarySortOrder = LibrarySortOrder.valueOf(this))
}
)
is MainEvent.OnChangeLibrarySortOrderDescending -> handleDatastoreUpdate(
key = DataStoreData.LIBRARY_SORT_ORDER_DESCENDING,
value = event.value,
updateState = {
it.copy(librarySortOrderDescending = this)
}
)
is MainEvent.OnChangeLibraryPerCategorySort -> handleDatastoreUpdate(
key = DataStoreData.LIBRARY_PER_CATEGORY_SORT,
value = event.value,
updateState = {
it.copy(libraryPerCategorySort = this)
}
)
}
}
fun init(settingsModelReady: StateFlow<Boolean>) {
viewModelScope.launch(Dispatchers.Main) {
val settings = getPreferencesUseCase()
/* All additional execution */
changeLanguagePreferenceUseCase(settings.language)
updateStateWithSavedHandle { settings }
mainModelReady.update { true }
}
val isReady = combine(
mainModelReady.asStateFlow(),
settingsModelReady
) { values ->
values.all { it }
}
viewModelScope.launch(Dispatchers.Main) {
isReady.first { bool ->
if (bool) {
_isReady.update {
true
}
}
bool
}
}
}
private fun handleLanguageUpdate(event: MainEvent.OnChangeLanguage) {
viewModelScope.launch(Dispatchers.Main.immediate) {
changeLanguagePreferenceUseCase(event.value)
updateStateWithSavedHandle {
it.copy(language = event.value)
}
}
}
private fun handleBrowseIncludedFilterItemUpdate(
event: MainEvent.OnChangeBrowseIncludedFilterItem
) {
val set = _state.value.browseIncludedFilterItems.toMutableSet()
if (!set.add(event.value)) {
set.remove(event.value)
}
handleDatastoreUpdate(
key = DataStoreData.BROWSE_INCLUDED_FILTER_ITEMS,
value = set,
updateState = {
it.copy(browseIncludedFilterItems = toList())
}
)
}
private fun handleBrowsePinnedPathsUpdate(
event: MainEvent.OnChangeBrowsePinnedPaths
) {
val set = _state.value.browsePinnedPaths.toMutableSet()
if (!set.add(event.value)) {
set.remove(event.value)
}
handleDatastoreUpdate(
key = DataStoreData.BROWSE_PINNED_PATHS,
value = set,
updateState = {
it.copy(browsePinnedPaths = toList())
}
)
}
/**
* Handles and updates Datastore.
*/
private fun <V> handleDatastoreUpdate(
key: Preferences.Key<V>,
value: V,
updateState: V.(MainState) -> MainState
) {
viewModelScope.launch(Dispatchers.Main.immediate) {
putPreferenceUseCase(key = key, value = value)
updateStateWithSavedHandle {
value.updateState(it)
}
}
}
/**
* Updates [MainState] along with [SavedStateHandle].
*/
private suspend fun updateStateWithSavedHandle(
function: (MainState) -> MainState
) {
withContext(Dispatchers.Main.immediate) {
_state.update {
stateHandle["main_state"] = function(it)
function(it)
}
}
}
private suspend inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
mutex.withLock {
yield()
this.value = function(this.value)
}
}
}

View file

@ -1,438 +0,0 @@
/*
* Book's Story free and open-source Material You eBook reader.
* Copyright (C) 2024-2025 Acclorite
* SPDX-License-Identifier: GPL-3.0-only
*/
@file:Suppress("UNCHECKED_CAST")
package ua.acclorite.book_story.presentation.main
import android.os.Parcelable
import androidx.annotation.Keep
import androidx.compose.runtime.Immutable
import androidx.datastore.preferences.core.Preferences
import kotlinx.parcelize.Parcelize
import ua.acclorite.book_story.core.data.CoreData
import ua.acclorite.book_story.presentation.browse.model.BrowseLayout
import ua.acclorite.book_story.presentation.browse.model.BrowseSortOrder
import ua.acclorite.book_story.presentation.library.model.LibraryLayout
import ua.acclorite.book_story.presentation.library.model.LibrarySortOrder
import ua.acclorite.book_story.presentation.library.model.LibraryTitlePosition
import ua.acclorite.book_story.presentation.main.data.DataStoreData
import ua.acclorite.book_story.presentation.main.model.DarkTheme
import ua.acclorite.book_story.presentation.main.model.HorizontalAlignment
import ua.acclorite.book_story.presentation.main.model.PureDark
import ua.acclorite.book_story.presentation.main.model.ThemeContrast
import ua.acclorite.book_story.presentation.reader.model.ReaderColorEffects
import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness
import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture
import ua.acclorite.book_story.presentation.reader.model.ReaderProgressCount
import ua.acclorite.book_story.presentation.reader.model.ReaderScreenOrientation
import ua.acclorite.book_story.presentation.reader.model.ReaderTextAlignment
import ua.acclorite.book_story.ui.reader.data.ReaderData
import ua.acclorite.book_story.ui.theme.Theme
import java.util.Locale
/**
* Main State.
* All app's settings/preferences/permanent-variables are here.
* Wrapped in SavedStateHandle, so it won't reset.
*/
@Immutable
@Keep
@Parcelize
data class MainState(
// General Settings
val language: String = provideDefaultValue {
val locale = Locale.getDefault().language.take(2)
CoreData.languages.any { locale == it.first }.run {
if (this) locale
else "en"// Default language.
}
},
val theme: Theme = provideDefaultValue { Theme.entries().first() },
val darkTheme: DarkTheme = provideDefaultValue { DarkTheme.FOLLOW_SYSTEM },
val pureDark: PureDark = provideDefaultValue { PureDark.OFF },
val absoluteDark: Boolean = provideDefaultValue { false },
val themeContrast: ThemeContrast = provideDefaultValue { ThemeContrast.STANDARD },
val showStartScreen: Boolean = provideDefaultValue { true },
val doublePressExit: Boolean = provideDefaultValue { false },
// Reader Settings
val fontFamily: String = provideDefaultValue { ReaderData.fonts[0].id },
val fontThickness: ReaderFontThickness = provideDefaultValue { ReaderFontThickness.NORMAL },
val isItalic: Boolean = provideDefaultValue { false },
val fontSize: Int = provideDefaultValue { 16 },
val lineHeight: Int = provideDefaultValue { 4 },
val paragraphHeight: Int = provideDefaultValue { 8 },
val paragraphIndentation: Int = provideDefaultValue { 0 },
val sidePadding: Int = provideDefaultValue { 6 },
val verticalPadding: Int = provideDefaultValue { 0 },
val doubleClickTranslation: Boolean = provideDefaultValue { false },
val fastColorPresetChange: Boolean = provideDefaultValue { true },
val textAlignment: ReaderTextAlignment = provideDefaultValue { ReaderTextAlignment.JUSTIFY },
val letterSpacing: Int = provideDefaultValue { 0 },
val cutoutPadding: Boolean = provideDefaultValue { false },
val fullscreen: Boolean = provideDefaultValue { true },
val keepScreenOn: Boolean = provideDefaultValue { true },
val hideBarsOnFastScroll: Boolean = provideDefaultValue { false },
val perceptionExpander: Boolean = provideDefaultValue { false },
val perceptionExpanderPadding: Int = provideDefaultValue { 5 },
val perceptionExpanderThickness: Int = provideDefaultValue { 4 },
val screenOrientation: ReaderScreenOrientation = provideDefaultValue {
ReaderScreenOrientation.DEFAULT
},
val customScreenBrightness: Boolean = provideDefaultValue { false },
val screenBrightness: Float = provideDefaultValue { 0.5f },
val horizontalGesture: ReaderHorizontalGesture = provideDefaultValue {
ReaderHorizontalGesture.OFF
},
val horizontalGestureScroll: Float = provideDefaultValue { 0.7f },
val horizontalGestureSensitivity: Float = provideDefaultValue { 0.6f },
val horizontalGestureAlphaAnim: Boolean = provideDefaultValue { true },
val horizontalGesturePullAnim: Boolean = provideDefaultValue { true },
val bottomBarPadding: Int = provideDefaultValue { 0 },
val highlightedReading: Boolean = provideDefaultValue { false },
val highlightedReadingThickness: Int = provideDefaultValue { 2 },
val chapterTitleAlignment: ReaderTextAlignment = provideDefaultValue { ReaderTextAlignment.JUSTIFY },
val images: Boolean = provideDefaultValue { true },
val imagesCaptions: Boolean = provideDefaultValue { true },
val imagesCornersRoundness: Int = provideDefaultValue { 8 },
val imagesAlignment: HorizontalAlignment = provideDefaultValue { HorizontalAlignment.START },
val imagesWidth: Float = provideDefaultValue { 0.8f },
val imagesColorEffects: ReaderColorEffects = provideDefaultValue { ReaderColorEffects.OFF },
val progressBar: Boolean = provideDefaultValue { false },
val progressBarPadding: Int = provideDefaultValue { 4 },
val progressBarAlignment: HorizontalAlignment = provideDefaultValue { HorizontalAlignment.CENTER },
val progressBarFontSize: Int = provideDefaultValue { 8 },
val progressCount: ReaderProgressCount = provideDefaultValue { ReaderProgressCount.PERCENTAGE },
// Library settings
val libraryLayout: LibraryLayout = provideDefaultValue { LibraryLayout.GRID },
val libraryAutoGridSize: Boolean = provideDefaultValue { true },
val libraryGridSize: Int = provideDefaultValue { 0 },
val libraryTitlePosition: LibraryTitlePosition = provideDefaultValue { LibraryTitlePosition.BELOW },
val libraryReadButton: Boolean = provideDefaultValue { true },
val libraryShowProgress: Boolean = provideDefaultValue { true },
val libraryShowBookCount: Boolean = provideDefaultValue { true },
val libraryShowCategoryTabs: Boolean = provideDefaultValue { true },
val libraryAlwaysShowDefaultTab: Boolean = provideDefaultValue { false },
val librarySortOrder: LibrarySortOrder = provideDefaultValue { LibrarySortOrder.LAST_READ },
val librarySortOrderDescending: Boolean = provideDefaultValue { true },
val libraryPerCategorySort: Boolean = provideDefaultValue { false },
// Browse Settings
val browseLayout: BrowseLayout = provideDefaultValue { BrowseLayout.LIST },
val browseAutoGridSize: Boolean = provideDefaultValue { true },
val browseGridSize: Int = provideDefaultValue { 0 },
val browseSortOrder: BrowseSortOrder = provideDefaultValue { BrowseSortOrder.LAST_MODIFIED },
val browseSortOrderDescending: Boolean = provideDefaultValue { true },
val browseIncludedFilterItems: List<String> = provideDefaultValue { emptyList() },
val browsePinnedPaths: List<String> = provideDefaultValue { emptyList() },
) : Parcelable {
companion object {
private fun <D> provideDefaultValue(calculation: () -> D): D {
return calculation()
}
/**
* Initializes [MainState] by given [Map].
* If no value provided in [data], assigns default value.
*/
fun initialize(data: Map<String, Any>): MainState {
val defaultState = MainState()
fun <V, T> provideValue(
key: Preferences.Key<T>,
convert: T.() -> V = { this as V },
default: MainState.() -> V
): V {
return (data[key.name] as? T)?.convert() ?: defaultState.default()
}
return DataStoreData.run {
MainState(
language = provideValue(
LANGUAGE
) { language },
theme = provideValue(
THEME, convert = { Theme.valueOf(this) }
) { theme },
darkTheme = provideValue(
DARK_THEME, convert = { DarkTheme.valueOf(this) }
) { darkTheme },
pureDark = provideValue(
PURE_DARK, convert = { PureDark.valueOf(this) }
) { pureDark },
absoluteDark = provideValue(
ABSOLUTE_DARK
) { absoluteDark },
themeContrast = provideValue(
THEME_CONTRAST, convert = { ThemeContrast.valueOf(this) }
) { themeContrast },
showStartScreen = provideValue(
SHOW_START_SCREEN
) { showStartScreen },
fontFamily = provideValue(
FONT
) { fontFamily },
isItalic = provideValue(
IS_ITALIC
) { isItalic },
fontSize = provideValue(
FONT_SIZE
) { fontSize },
lineHeight = provideValue(
LINE_HEIGHT
) { lineHeight },
paragraphHeight = provideValue(
PARAGRAPH_HEIGHT
) { paragraphHeight },
paragraphIndentation = provideValue(
PARAGRAPH_INDENTATION
) { paragraphIndentation },
sidePadding = provideValue(
SIDE_PADDING
) { sidePadding },
doubleClickTranslation = provideValue(
DOUBLE_CLICK_TRANSLATION
) { doubleClickTranslation },
fastColorPresetChange = provideValue(
FAST_COLOR_PRESET_CHANGE
) { fastColorPresetChange },
browseLayout = provideValue(
BROWSE_LAYOUT, convert = { BrowseLayout.valueOf(this) }
) { browseLayout },
browseAutoGridSize = provideValue(
BROWSE_AUTO_GRID_SIZE
) { browseAutoGridSize },
browseGridSize = provideValue(
BROWSE_GRID_SIZE
) { browseGridSize },
browseSortOrder = provideValue(
BROWSE_SORT_ORDER, convert = { BrowseSortOrder.valueOf(this) }
) { browseSortOrder },
browseSortOrderDescending = provideValue(
BROWSE_SORT_ORDER_DESCENDING
) { browseSortOrderDescending },
browseIncludedFilterItems = provideValue(
BROWSE_INCLUDED_FILTER_ITEMS, convert = { toList() }
) { browseIncludedFilterItems },
textAlignment = provideValue(
TEXT_ALIGNMENT, convert = { ReaderTextAlignment.valueOf(this) }
) { textAlignment },
doublePressExit = provideValue(
DOUBLE_PRESS_EXIT
) { doublePressExit },
letterSpacing = provideValue(
LETTER_SPACING
) { letterSpacing },
cutoutPadding = provideValue(
CUTOUT_PADDING
) { cutoutPadding },
fullscreen = provideValue(
FULLSCREEN
) { fullscreen },
keepScreenOn = provideValue(
KEEP_SCREEN_ON
) { keepScreenOn },
verticalPadding = provideValue(
VERTICAL_PADDING
) { verticalPadding },
hideBarsOnFastScroll = provideValue(
HIDE_BARS_ON_FAST_SCROLL
) { hideBarsOnFastScroll },
perceptionExpander = provideValue(
PERCEPTION_EXPANDER
) { perceptionExpander },
perceptionExpanderPadding = provideValue(
PERCEPTION_EXPANDER_PADDING
) { perceptionExpanderPadding },
perceptionExpanderThickness = provideValue(
PERCEPTION_EXPANDER_THICKNESS
) { perceptionExpanderThickness },
screenOrientation = provideValue(
SCREEN_ORIENTATION, convert = { ReaderScreenOrientation.valueOf(this) }
) { screenOrientation },
customScreenBrightness = provideValue(
CUSTOM_SCREEN_BRIGHTNESS
) { customScreenBrightness },
screenBrightness = provideValue(
SCREEN_BRIGHTNESS, convert = { this.toFloat() }
) { screenBrightness },
horizontalGesture = provideValue(
HORIZONTAL_GESTURE, convert = { ReaderHorizontalGesture.valueOf(this) }
) { horizontalGesture },
horizontalGestureScroll = provideValue(
HORIZONTAL_GESTURE_SCROLL, convert = { toFloat() }
) { horizontalGestureScroll },
horizontalGestureSensitivity = provideValue(
HORIZONTAL_GESTURE_SENSITIVITY, convert = { toFloat() }
) { horizontalGestureSensitivity },
bottomBarPadding = provideValue(
BOTTOM_BAR_PADDING
) { bottomBarPadding },
highlightedReading = provideValue(
HIGHLIGHTED_READING
) { highlightedReading },
highlightedReadingThickness = provideValue(
HIGHLIGHTED_READING_THICKNESS
) { highlightedReadingThickness },
chapterTitleAlignment = provideValue(
CHAPTER_TITLE_ALIGNMENT, convert = { ReaderTextAlignment.valueOf(this) }
) { chapterTitleAlignment },
images = provideValue(
IMAGES
) { images },
imagesCaptions = provideValue(
IMAGES_CAPTIONS
) { imagesCaptions },
imagesCornersRoundness = provideValue(
IMAGES_CORNERS_ROUNDNESS
) { imagesCornersRoundness },
imagesAlignment = provideValue(
IMAGES_ALIGNMENT, convert = { HorizontalAlignment.valueOf(this) }
) { imagesAlignment },
imagesWidth = provideValue(
IMAGES_WIDTH, convert = { toFloat() }
) { imagesWidth },
imagesColorEffects = provideValue(
IMAGES_COLOR_EFFECTS, convert = { ReaderColorEffects.valueOf(this) }
) { imagesColorEffects },
progressBar = provideValue(
PROGRESS_BAR
) { progressBar },
progressBarPadding = provideValue(
PROGRESS_BAR_PADDING
) { progressBarPadding },
progressBarAlignment = provideValue(
PROGRESS_BAR_ALIGNMENT, convert = { HorizontalAlignment.valueOf(this) }
) { progressBarAlignment },
progressBarFontSize = provideValue(
PROGRESS_BAR_FONT_SIZE
) { progressBarFontSize },
browsePinnedPaths = provideValue(
BROWSE_PINNED_PATHS, convert = { toList() }
) { browsePinnedPaths },
fontThickness = provideValue(
FONT_THICKNESS, convert = { ReaderFontThickness.valueOf(this) }
) { fontThickness },
progressCount = provideValue(
PROGRESS_COUNT, convert = { ReaderProgressCount.valueOf(this) }
) { progressCount },
horizontalGestureAlphaAnim = provideValue(
HORIZONTAL_GESTURE_ALPHA_ANIM
) { horizontalGestureAlphaAnim },
horizontalGesturePullAnim = provideValue(
HORIZONTAL_GESTURE_PULL_ANIM
) { horizontalGesturePullAnim },
libraryLayout = provideValue(
LIBRARY_LAYOUT, convert = { LibraryLayout.valueOf(this) }
) { libraryLayout },
libraryAutoGridSize = provideValue(
LIBRARY_AUTO_GRID_SIZE
) { libraryAutoGridSize },
libraryGridSize = provideValue(
LIBRARY_GRID_SIZE
) { libraryGridSize },
libraryReadButton = provideValue(
LIBRARY_READ_BUTTON
) { libraryReadButton },
libraryShowProgress = provideValue(
LIBRARY_SHOW_PROGRESS
) { libraryShowProgress },
libraryTitlePosition = provideValue(
LIBRARY_TITLE_POSITION, convert = { LibraryTitlePosition.valueOf(this) }
) { libraryTitlePosition },
libraryShowBookCount = provideValue(
LIBRARY_SHOW_BOOK_COUNT
) { libraryShowBookCount },
libraryShowCategoryTabs = provideValue(
LIBRARY_SHOW_CATEGORY_TABS
) { libraryShowCategoryTabs },
libraryAlwaysShowDefaultTab = provideValue(
LIBRARY_ALWAYS_SHOW_DEFAULT_TAB
) { libraryAlwaysShowDefaultTab },
librarySortOrder = provideValue(
LIBRARY_SORT_ORDER, convert = { LibrarySortOrder.valueOf(this) }
) { librarySortOrder },
librarySortOrderDescending = provideValue(
LIBRARY_SORT_ORDER_DESCENDING
) { librarySortOrderDescending },
libraryPerCategorySort = provideValue(
LIBRARY_PER_CATEGORY_SORT
) { libraryPerCategorySort },
)
}
}
}
}

View file

@ -1,93 +0,0 @@
/*
* 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.main.data
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.doublePreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
object DataStoreData {
// General settings
val LANGUAGE = stringPreferencesKey("language")
val THEME = stringPreferencesKey("theme")
val DARK_THEME = stringPreferencesKey("dark_theme")
val PURE_DARK = stringPreferencesKey("pure_dark")
val ABSOLUTE_DARK = booleanPreferencesKey("absolute_dark")
val THEME_CONTRAST = stringPreferencesKey("theme_contrast")
val SHOW_START_SCREEN = booleanPreferencesKey("guide")
val DOUBLE_PRESS_EXIT = booleanPreferencesKey("double_press_exit")
// Reader settings
val DOUBLE_CLICK_TRANSLATION = booleanPreferencesKey("double_click_translation")
val FAST_COLOR_PRESET_CHANGE = booleanPreferencesKey("fast_color_preset_change")
val SIDE_PADDING = intPreferencesKey("side_padding")
val VERTICAL_PADDING = intPreferencesKey("vertical_padding")
val FONT = stringPreferencesKey("font")
val FONT_THICKNESS = stringPreferencesKey("font_thickness")
val IS_ITALIC = booleanPreferencesKey("font_style")
val FONT_SIZE = intPreferencesKey("font_size")
val LINE_HEIGHT = intPreferencesKey("line_height")
val PARAGRAPH_HEIGHT = intPreferencesKey("paragraph_height")
val PARAGRAPH_INDENTATION = intPreferencesKey("paragraph_indentation_int")
val TEXT_ALIGNMENT = stringPreferencesKey("text_alignment")
val LETTER_SPACING = intPreferencesKey("letter_spacing")
val CUTOUT_PADDING = booleanPreferencesKey("cutout_padding")
val FULLSCREEN = booleanPreferencesKey("fullscreen")
val KEEP_SCREEN_ON = booleanPreferencesKey("keep_screen_on")
val HIDE_BARS_ON_FAST_SCROLL = booleanPreferencesKey("hide_bars_on_fast_scroll")
val PERCEPTION_EXPANDER = booleanPreferencesKey("perception_expander")
val PERCEPTION_EXPANDER_PADDING = intPreferencesKey("perception_expander_padding")
val PERCEPTION_EXPANDER_THICKNESS = intPreferencesKey("perception_expander_thickness")
val SCREEN_ORIENTATION = stringPreferencesKey("screen_orientation")
val CUSTOM_SCREEN_BRIGHTNESS = booleanPreferencesKey("custom_screen_brightness")
val SCREEN_BRIGHTNESS = doublePreferencesKey("screen_brightness")
val HORIZONTAL_GESTURE = stringPreferencesKey("horizontal_gesture")
val HORIZONTAL_GESTURE_SCROLL = doublePreferencesKey("horizontal_gesture_scroll")
val HORIZONTAL_GESTURE_SENSITIVITY = doublePreferencesKey("horizontal_gesture_sensitivity")
val HORIZONTAL_GESTURE_ALPHA_ANIM = booleanPreferencesKey("horizontal_gesture_alpha_anim_bool")
val HORIZONTAL_GESTURE_PULL_ANIM = booleanPreferencesKey("horizontal_gesture_pull_anim")
val BOTTOM_BAR_PADDING = intPreferencesKey("bottom_bar_padding")
val HIGHLIGHTED_READING = booleanPreferencesKey("highlighted_reading")
val HIGHLIGHTED_READING_THICKNESS = intPreferencesKey("highlighted_reading_thickness")
val CHAPTER_TITLE_ALIGNMENT = stringPreferencesKey("chapter_title_alignment")
val IMAGES = booleanPreferencesKey("images")
val IMAGES_CAPTIONS = booleanPreferencesKey("images_captions")
val IMAGES_CORNERS_ROUNDNESS = intPreferencesKey("images_corners_roundness")
val IMAGES_ALIGNMENT = stringPreferencesKey("images_alignment")
val IMAGES_WIDTH = doublePreferencesKey("images_width")
val IMAGES_COLOR_EFFECTS = stringPreferencesKey("images_color_effects")
val PROGRESS_BAR = booleanPreferencesKey("progress_bar")
val PROGRESS_BAR_PADDING = intPreferencesKey("progress_bar_padding")
val PROGRESS_BAR_ALIGNMENT = stringPreferencesKey("progress_bar_alignment")
val PROGRESS_BAR_FONT_SIZE = intPreferencesKey("progress_bar_font_size")
val PROGRESS_COUNT = stringPreferencesKey("progress_count")
// Library settings
val LIBRARY_LAYOUT = stringPreferencesKey("library_layout")
val LIBRARY_AUTO_GRID_SIZE = booleanPreferencesKey("library_auto_grid_size")
val LIBRARY_GRID_SIZE = intPreferencesKey("library_grid_size")
val LIBRARY_READ_BUTTON = booleanPreferencesKey("library_read_button")
val LIBRARY_SHOW_PROGRESS = booleanPreferencesKey("library_show_progress")
val LIBRARY_TITLE_POSITION = stringPreferencesKey("library_title_position")
val LIBRARY_SHOW_BOOK_COUNT = booleanPreferencesKey("library_show_book_count")
val LIBRARY_SHOW_CATEGORY_TABS = booleanPreferencesKey("library_show_category_tabs")
val LIBRARY_ALWAYS_SHOW_DEFAULT_TAB = booleanPreferencesKey("library_always_show_default_tab")
val LIBRARY_SORT_ORDER = stringPreferencesKey("library_sort_order")
val LIBRARY_SORT_ORDER_DESCENDING = booleanPreferencesKey("library_sort_order_descending")
val LIBRARY_PER_CATEGORY_SORT = booleanPreferencesKey("library_per_category_sort")
// Browse settings
val BROWSE_LAYOUT = stringPreferencesKey("browse_layout")
val BROWSE_AUTO_GRID_SIZE = booleanPreferencesKey("browse_auto_grid_size")
val BROWSE_GRID_SIZE = intPreferencesKey("browse_grid_size")
val BROWSE_SORT_ORDER = stringPreferencesKey("browse_sort_order")
val BROWSE_SORT_ORDER_DESCENDING = booleanPreferencesKey("browse_sort_order_descending")
val BROWSE_INCLUDED_FILTER_ITEMS = stringSetPreferencesKey("browse_included_filter_items")
val BROWSE_PINNED_PATHS = stringSetPreferencesKey("browse_pinned_paths")
}

View file

@ -46,17 +46,16 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import ua.acclorite.book_story.core.helpers.calculateProgress import ua.acclorite.book_story.core.helpers.calculateProgress
import ua.acclorite.book_story.presentation.book_info.BookInfoScreen import ua.acclorite.book_story.presentation.book_info.BookInfoScreen
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.navigator.Screen import ua.acclorite.book_story.presentation.navigator.Screen
import ua.acclorite.book_story.presentation.reader.model.ReaderColorEffects import ua.acclorite.book_story.presentation.reader.model.ReaderColorEffects
import ua.acclorite.book_story.presentation.reader.model.ReaderProgressCount import ua.acclorite.book_story.presentation.reader.model.ReaderProgressCount
import ua.acclorite.book_story.presentation.reader.model.ReaderTextAlignment import ua.acclorite.book_story.presentation.reader.model.ReaderTextAlignment
import ua.acclorite.book_story.presentation.settings.SettingsModel import ua.acclorite.book_story.presentation.settings.SettingsModel
import ua.acclorite.book_story.ui.common.helpers.LocalActivity import ua.acclorite.book_story.ui.common.helpers.LocalActivity
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.helpers.setBrightness import ua.acclorite.book_story.ui.common.helpers.setBrightness
import ua.acclorite.book_story.ui.navigator.LocalNavigator import ua.acclorite.book_story.ui.navigator.LocalNavigator
import ua.acclorite.book_story.ui.reader.ReaderContent import ua.acclorite.book_story.ui.reader.ReaderContent
import ua.acclorite.book_story.ui.reader.data.ReaderData
import kotlin.math.roundToInt import kotlin.math.roundToInt
@Parcelize @Parcelize
@ -72,11 +71,10 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
override fun Content() { override fun Content() {
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val screenModel = hiltViewModel<ReaderModel>() val screenModel = hiltViewModel<ReaderModel>()
val mainModel = hiltViewModel<MainModel>()
val settingsModel = hiltViewModel<SettingsModel>() val settingsModel = hiltViewModel<SettingsModel>()
val settings = LocalSettings.current
val state = screenModel.state.collectAsStateWithLifecycle() val state = screenModel.state.collectAsStateWithLifecycle()
val mainState = mainModel.state.collectAsStateWithLifecycle()
val settingsState = settingsModel.state.collectAsStateWithLifecycle() val settingsState = settingsModel.state.collectAsStateWithLifecycle()
val activity = LocalActivity.current val activity = LocalActivity.current
@ -99,12 +97,12 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
if (velocity in -70f..70f) return@let if (velocity in -70f..70f) return@let
if (!state.value.showMenu) return@let if (!state.value.showMenu) return@let
if (state.value.lockMenu) return@let if (state.value.lockMenu) return@let
if (!mainState.value.hideBarsOnFastScroll) return@let if (!settings.hideBarsOnFastScroll.lastValue) return@let
screenModel.onEvent( screenModel.onEvent(
ReaderEvent.OnMenuVisibility( ReaderEvent.OnMenuVisibility(
show = false, show = false,
fullscreenMode = mainState.value.fullscreen, fullscreenMode = settings.fullscreen.lastValue,
saveCheckpoint = false, saveCheckpoint = false,
activity = activity activity = activity
) )
@ -116,13 +114,6 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
} }
} }
val fontFamily = remember(mainState.value.fontFamily) {
ReaderData.fonts.run {
find {
it.id == mainState.value.fontFamily
} ?: get(0)
}
}
val backgroundColor = animateColorAsState( val backgroundColor = animateColorAsState(
targetValue = settingsState.value.selectedColorPreset.backgroundColor targetValue = settingsState.value.selectedColorPreset.backgroundColor
) )
@ -130,89 +121,87 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
targetValue = settingsState.value.selectedColorPreset.fontColor targetValue = settingsState.value.selectedColorPreset.fontColor
) )
val lineHeight = remember( val lineHeight = remember(
mainState.value.fontSize, settings.fontSize.value,
mainState.value.lineHeight settings.lineHeight.value
) { ) {
(mainState.value.fontSize + mainState.value.lineHeight).sp (settings.fontSize.lastValue + settings.lineHeight.lastValue).sp
} }
val letterSpacing = remember(mainState.value.letterSpacing) { val letterSpacing = remember(settings.letterSpacing.value) {
(mainState.value.letterSpacing / 100f).em (settings.letterSpacing.lastValue / 100f).em
} }
val sidePadding = remember(mainState.value.sidePadding) { val sidePadding = remember(settings.sidePadding.value) {
(mainState.value.sidePadding * 3).dp (settings.sidePadding.lastValue * 3).dp
} }
val verticalPadding = remember(mainState.value.verticalPadding) { val verticalPadding = remember(settings.verticalPadding.value) {
(mainState.value.verticalPadding * 4.5f).dp (settings.verticalPadding.lastValue * 4.5f).dp
} }
val paragraphHeight = remember( val paragraphHeight = remember(
mainState.value.paragraphHeight, settings.paragraphHeight.value,
mainState.value.lineHeight settings.lineHeight.value
) { ) {
((mainState.value.paragraphHeight * 3).dp).coerceAtLeast( ((settings.paragraphHeight.lastValue * 3).dp).coerceAtLeast(
with(density) { mainState.value.lineHeight.sp.toDp().value * 0.5f }.dp with(density) { settings.lineHeight.lastValue.sp.toDp().value * 0.5f }.dp
) )
} }
val fontStyle = remember(mainState.value.isItalic) { val fontStyle = remember(settings.italic.value) {
when (mainState.value.isItalic) { when (settings.italic.lastValue) {
true -> FontStyle.Italic true -> FontStyle.Italic
false -> FontStyle.Normal false -> FontStyle.Normal
} }
} }
val paragraphIndentation = remember( val paragraphIndentation = remember(
mainState.value.paragraphIndentation, settings.paragraphIndentation.value,
mainState.value.textAlignment settings.textAlignment.value
) { ) {
if ( if (
mainState.value.textAlignment == ReaderTextAlignment.CENTER || settings.textAlignment.lastValue == ReaderTextAlignment.CENTER ||
mainState.value.textAlignment == ReaderTextAlignment.END settings.textAlignment.lastValue == ReaderTextAlignment.END
) { ) return@remember 0.sp
return@remember 0.sp (settings.paragraphIndentation.lastValue * 6).sp
}
(mainState.value.paragraphIndentation * 6).sp
} }
val perceptionExpanderPadding = remember( val perceptionExpanderPadding = remember(
sidePadding, sidePadding,
mainState.value.perceptionExpanderPadding settings.perceptionExpanderPadding.value
) { ) {
sidePadding + (mainState.value.perceptionExpanderPadding * 8).dp sidePadding + (settings.perceptionExpanderPadding.lastValue * 8).dp
} }
val perceptionExpanderThickness = remember( val perceptionExpanderThickness = remember(
mainState.value.perceptionExpanderThickness settings.perceptionExpanderThickness.value
) { ) {
(mainState.value.perceptionExpanderThickness * 0.25f).dp (settings.perceptionExpanderThickness.lastValue * 0.25f).dp
} }
val horizontalGestureSensitivity = remember(mainState.value.horizontalGestureSensitivity) { val horizontalGestureSensitivity = remember(settings.horizontalGestureSensitivity.value) {
(36f + mainState.value.horizontalGestureSensitivity * (4f - 36f)).dp (36f + settings.horizontalGestureSensitivity.lastValue * (4f - 36f)).dp
} }
val highlightedReadingThickness = remember(mainState.value.highlightedReadingThickness) { val highlightedReadingThickness = remember(settings.highlightedReadingThickness.value) {
when (mainState.value.highlightedReadingThickness) { when (settings.highlightedReadingThickness.lastValue) {
2 -> FontWeight.SemiBold 2 -> FontWeight.SemiBold
3 -> FontWeight.Bold 3 -> FontWeight.Bold
else -> FontWeight.Medium else -> FontWeight.Medium
} }
} }
val horizontalAlignment = remember(mainState.value.textAlignment) { val horizontalAlignment = remember(settings.textAlignment.value) {
when (mainState.value.textAlignment) { when (settings.textAlignment.lastValue) {
ReaderTextAlignment.START, ReaderTextAlignment.JUSTIFY -> Alignment.Start ReaderTextAlignment.START, ReaderTextAlignment.JUSTIFY -> Alignment.Start
ReaderTextAlignment.CENTER -> Alignment.CenterHorizontally ReaderTextAlignment.CENTER -> Alignment.CenterHorizontally
ReaderTextAlignment.END -> Alignment.End ReaderTextAlignment.END -> Alignment.End
} }
} }
val imagesWidth = remember(mainState.value.imagesWidth) { val imagesWidth = remember(settings.imagesWidth.value) {
mainState.value.imagesWidth.coerceAtLeast(0.01f) settings.imagesWidth.lastValue.coerceAtLeast(0.01f)
} }
val imagesCornersRoundness = remember( val imagesCornersRoundness = remember(
mainState.value.imagesCornersRoundness, settings.imagesCornersRoundness.value,
mainState.value.imagesWidth settings.imagesWidth.value
) { ) {
(mainState.value.imagesCornersRoundness * 3 * imagesWidth).dp (settings.imagesCornersRoundness.lastValue * 3 * imagesWidth).dp
} }
val imagesColorEffects = remember( val imagesColorEffects = remember(
mainState.value.imagesColorEffects, settings.imagesColorEffects.value,
fontColor.value, fontColor.value,
backgroundColor.value backgroundColor.value
) { ) {
when (mainState.value.imagesColorEffects) { when (settings.imagesColorEffects.lastValue) {
ReaderColorEffects.OFF -> null ReaderColorEffects.OFF -> null
ReaderColorEffects.GRAYSCALE -> ColorFilter.colorMatrix( ReaderColorEffects.GRAYSCALE -> ColorFilter.colorMatrix(
@ -230,21 +219,21 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
) )
} }
} }
val progressBarPadding = remember(mainState.value.progressBarPadding) { val progressBarPadding = remember(settings.progressBarPadding.value) {
(mainState.value.progressBarPadding * 3).dp (settings.progressBarPadding.lastValue * 3).dp
} }
val progressBarFontSize = remember(mainState.value.progressBarFontSize) { val progressBarFontSize = remember(settings.progressBarFontSize.value) {
(mainState.value.progressBarFontSize * 2).sp (settings.progressBarFontSize.lastValue * 2).sp
} }
val layoutDirection = LocalLayoutDirection.current val layoutDirection = LocalLayoutDirection.current
val cutoutInsets = WindowInsets.displayCutout val cutoutInsets = WindowInsets.displayCutout
val systemBarsInsets = WindowInsets.systemBarsIgnoringVisibility val systemBarsInsets = WindowInsets.systemBarsIgnoringVisibility
val cutoutInsetsPadding = remember(mainState.value.cutoutPadding) { val cutoutInsetsPadding = remember(settings.cutoutPadding.value) {
derivedStateOf { derivedStateOf {
cutoutInsets.asPaddingValues(density = density).run { cutoutInsets.asPaddingValues(density = density).run {
if (mainState.value.cutoutPadding) PaddingValues( if (settings.cutoutPadding.lastValue) PaddingValues(
top = calculateTopPadding(), top = calculateTopPadding(),
start = calculateStartPadding(layoutDirection), start = calculateStartPadding(layoutDirection),
end = calculateEndPadding(layoutDirection), end = calculateEndPadding(layoutDirection),
@ -253,10 +242,10 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
} }
} }
} }
val systemBarsInsetsPadding = remember(mainState.value.fullscreen) { val systemBarsInsetsPadding = remember(settings.fullscreen.value) {
derivedStateOf { derivedStateOf {
systemBarsInsets.asPaddingValues(density = density).run { systemBarsInsets.asPaddingValues(density = density).run {
if (!mainState.value.fullscreen) PaddingValues( if (!settings.fullscreen.lastValue) PaddingValues(
top = calculateTopPadding(), top = calculateTopPadding(),
start = calculateStartPadding(layoutDirection), start = calculateStartPadding(layoutDirection),
end = calculateEndPadding(layoutDirection), end = calculateEndPadding(layoutDirection),
@ -292,16 +281,16 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
} }
) )
} }
val bottomBarPadding = remember(mainState.value.bottomBarPadding) { val bottomBarPadding = remember(settings.bottomBarPadding.value) {
(mainState.value.bottomBarPadding * 4f).dp (settings.bottomBarPadding.lastValue * 4f).dp
} }
val bookProgress = remember( val bookProgress = remember(
state.value.book.progress, state.value.book.progress,
state.value.text, state.value.text,
mainState.value.progressCount settings.progressCount.value
) { ) {
when (mainState.value.progressCount) { when (settings.progressCount.lastValue) {
ReaderProgressCount.PERCENTAGE -> { ReaderProgressCount.PERCENTAGE -> {
"${state.value.book.progress.calculateProgress(2)}%" "${state.value.book.progress.calculateProgress(2)}%"
} }
@ -318,10 +307,10 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
state.value.book.progress, state.value.book.progress,
state.value.currentChapter, state.value.currentChapter,
state.value.currentChapterProgress, state.value.currentChapterProgress,
mainState.value.progressCount settings.progressCount.value
) { ) {
if (state.value.currentChapter == null) return@remember "" if (state.value.currentChapter == null) return@remember ""
when (mainState.value.progressCount) { when (settings.progressCount.lastValue) {
ReaderProgressCount.PERCENTAGE -> { ReaderProgressCount.PERCENTAGE -> {
" (${state.value.currentChapterProgress.calculateProgress(2)}%)" " (${state.value.currentChapterProgress.calculateProgress(2)}%)"
} }
@ -341,18 +330,18 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
screenModel.init( screenModel.init(
bookId = bookId, bookId = bookId,
fullscreenMode = mainState.value.fullscreen, fullscreenMode = settings.fullscreen.lastValue,
activity = activity, activity = activity,
navigateBack = { navigateBack = {
navigator.pop() navigator.pop()
} }
) )
} }
LaunchedEffect(mainState.value.fullscreen) { LaunchedEffect(settings.fullscreen.value) {
screenModel.onEvent( screenModel.onEvent(
ReaderEvent.OnMenuVisibility( ReaderEvent.OnMenuVisibility(
show = state.value.showMenu, show = state.value.showMenu,
fullscreenMode = mainState.value.fullscreen, fullscreenMode = settings.fullscreen.lastValue,
saveCheckpoint = false, saveCheckpoint = false,
activity = activity activity = activity
) )
@ -362,18 +351,18 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
screenModel.updateProgress(listState) screenModel.updateProgress(listState)
} }
DisposableEffect(mainState.value.screenOrientation) { DisposableEffect(settings.screenOrientation.value) {
activity.requestedOrientation = mainState.value.screenOrientation.code activity.requestedOrientation = settings.screenOrientation.lastValue.code
onDispose { onDispose {
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
} }
} }
DisposableEffect( DisposableEffect(
mainState.value.screenBrightness, settings.screenBrightness.value,
mainState.value.customScreenBrightness settings.customScreenBrightness.value
) { ) {
when (mainState.value.customScreenBrightness) { when (settings.customScreenBrightness.lastValue) {
true -> activity.setBrightness(brightness = mainState.value.screenBrightness) true -> activity.setBrightness(brightness = settings.screenBrightness.lastValue)
false -> activity.setBrightness(brightness = null) false -> activity.setBrightness(brightness = null)
} }
@ -381,8 +370,8 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
activity.setBrightness(brightness = null) activity.setBrightness(brightness = null)
} }
} }
DisposableEffect(mainState.value.keepScreenOn) { DisposableEffect(settings.keepScreenOn.value) {
when (mainState.value.keepScreenOn) { when (settings.keepScreenOn.lastValue) {
true -> activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) true -> activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
false -> activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) false -> activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} }
@ -409,8 +398,8 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
listState = listState, listState = listState,
currentChapter = state.value.currentChapter, currentChapter = state.value.currentChapter,
nestedScrollConnection = nestedScrollConnection.value, nestedScrollConnection = nestedScrollConnection.value,
fastColorPresetChange = mainState.value.fastColorPresetChange, fastColorPresetChange = settings.fastColorPresetChange.value,
perceptionExpander = mainState.value.perceptionExpander, perceptionExpander = settings.perceptionExpander.value,
perceptionExpanderPadding = perceptionExpanderPadding, perceptionExpanderPadding = perceptionExpanderPadding,
perceptionExpanderThickness = perceptionExpanderThickness, perceptionExpanderThickness = perceptionExpanderThickness,
currentChapterProgress = state.value.currentChapterProgress, currentChapterProgress = state.value.currentChapterProgress,
@ -421,41 +410,41 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
lockMenu = state.value.lockMenu, lockMenu = state.value.lockMenu,
contentPadding = contentPadding, contentPadding = contentPadding,
verticalPadding = verticalPadding, verticalPadding = verticalPadding,
horizontalGesture = mainState.value.horizontalGesture, horizontalGesture = settings.horizontalGesture.value,
horizontalGestureScroll = mainState.value.horizontalGestureScroll, horizontalGestureScroll = settings.horizontalGestureScroll.value,
horizontalGestureSensitivity = horizontalGestureSensitivity, horizontalGestureSensitivity = horizontalGestureSensitivity,
horizontalGestureAlphaAnim = mainState.value.horizontalGestureAlphaAnim, horizontalGestureAlphaAnim = settings.horizontalGestureAlphaAnim.value,
horizontalGesturePullAnim = mainState.value.horizontalGesturePullAnim, horizontalGesturePullAnim = settings.horizontalGesturePullAnim.value,
highlightedReading = mainState.value.highlightedReading, highlightedReading = settings.highlightedReading.value,
highlightedReadingThickness = highlightedReadingThickness, highlightedReadingThickness = highlightedReadingThickness,
progress = progress, progress = progress,
progressBar = mainState.value.progressBar, progressBar = settings.progressBar.value,
progressBarPadding = progressBarPadding, progressBarPadding = progressBarPadding,
progressBarAlignment = mainState.value.progressBarAlignment, progressBarAlignment = settings.progressBarAlignment.value,
progressBarFontSize = progressBarFontSize, progressBarFontSize = progressBarFontSize,
paragraphHeight = paragraphHeight, paragraphHeight = paragraphHeight,
sidePadding = sidePadding, sidePadding = sidePadding,
bottomBarPadding = bottomBarPadding, bottomBarPadding = bottomBarPadding,
backgroundColor = backgroundColor.value, backgroundColor = backgroundColor.value,
fontColor = fontColor.value, fontColor = fontColor.value,
images = mainState.value.images, images = settings.images.value,
imagesCaptions = mainState.value.imagesCaptions, imagesCaptions = settings.imagesCaptions.value,
imagesCornersRoundness = imagesCornersRoundness, imagesCornersRoundness = imagesCornersRoundness,
imagesAlignment = mainState.value.imagesAlignment, imagesAlignment = settings.imagesAlignment.value,
imagesWidth = imagesWidth, imagesWidth = imagesWidth,
imagesColorEffects = imagesColorEffects, imagesColorEffects = imagesColorEffects,
fontFamily = fontFamily, fontFamily = settings.fontFamily.value,
lineHeight = lineHeight, lineHeight = lineHeight,
fontThickness = mainState.value.fontThickness, fontThickness = settings.fontThickness.value,
fontStyle = fontStyle, fontStyle = fontStyle,
chapterTitleAlignment = mainState.value.chapterTitleAlignment, chapterTitleAlignment = settings.chapterTitleAlignment.value,
textAlignment = mainState.value.textAlignment, textAlignment = settings.textAlignment.value,
horizontalAlignment = horizontalAlignment, horizontalAlignment = horizontalAlignment,
fontSize = mainState.value.fontSize.sp, fontSize = settings.fontSize.value.sp,
letterSpacing = letterSpacing, letterSpacing = letterSpacing,
paragraphIndentation = paragraphIndentation, paragraphIndentation = paragraphIndentation,
doubleClickTranslation = mainState.value.doubleClickTranslation, doubleClickTranslation = settings.doubleClickTranslation.value,
fullscreenMode = mainState.value.fullscreen, fullscreenMode = settings.fullscreen.value,
selectPreviousPreset = settingsModel::onEvent, selectPreviousPreset = settingsModel::onEvent,
selectNextPreset = settingsModel::onEvent, selectNextPreset = settingsModel::onEvent,
leave = screenModel::onEvent, leave = screenModel::onEvent,

View file

@ -16,6 +16,10 @@ import ua.acclorite.book_story.presentation.library.model.LibrarySortOrder
@Immutable @Immutable
sealed class SettingsEvent { sealed class SettingsEvent {
data class OnUpdateLanguage(
val language: String
) : SettingsEvent()
data class OnGrantPersistableUriPermission( data class OnGrantPersistableUriPermission(
val uri: Uri val uri: Uri
) : SettingsEvent() ) : SettingsEvent()

View file

@ -36,12 +36,14 @@ import ua.acclorite.book_story.domain.use_case.color_preset.SelectColorPresetUse
import ua.acclorite.book_story.domain.use_case.color_preset.UpdateColorPresetUseCase import ua.acclorite.book_story.domain.use_case.color_preset.UpdateColorPresetUseCase
import ua.acclorite.book_story.domain.use_case.permission.GrantPersistableUriPermissionUseCase 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.permission.ReleasePersistableUriPermissionUseCase
import ua.acclorite.book_story.domain.use_case.settings.UpdateLanguageUseCase
import ua.acclorite.book_story.ui.common.helpers.showToast import ua.acclorite.book_story.ui.common.helpers.showToast
import javax.inject.Inject import javax.inject.Inject
import kotlin.random.Random import kotlin.random.Random
@HiltViewModel @HiltViewModel
class SettingsModel @Inject constructor( class SettingsModel @Inject constructor(
private val updateLanguageUseCase: UpdateLanguageUseCase,
private val getColorPresetsUseCase: GetColorPresetsUseCase, private val getColorPresetsUseCase: GetColorPresetsUseCase,
private val updateColorPresetUseCase: UpdateColorPresetUseCase, private val updateColorPresetUseCase: UpdateColorPresetUseCase,
private val selectColorPresetUseCase: SelectColorPresetUseCase, private val selectColorPresetUseCase: SelectColorPresetUseCase,
@ -112,6 +114,8 @@ class SettingsModel @Inject constructor(
fun onEvent(event: SettingsEvent) { fun onEvent(event: SettingsEvent) {
when (event) { when (event) {
is SettingsEvent.OnUpdateLanguage -> updateLanguageUseCase(event.language)
is SettingsEvent.OnGrantPersistableUriPermission -> { is SettingsEvent.OnGrantPersistableUriPermission -> {
viewModelScope.launch { viewModelScope.launch {
grantPersistableUriPermissionUseCase( grantPersistableUriPermissionUseCase(

View file

@ -14,17 +14,17 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import ua.acclorite.book_story.core.data.CoreData import ua.acclorite.book_story.core.data.CoreData
import ua.acclorite.book_story.presentation.browse.BrowseScreen import ua.acclorite.book_story.presentation.browse.BrowseScreen
import ua.acclorite.book_story.presentation.help.HelpScreen import ua.acclorite.book_story.presentation.help.HelpScreen
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.navigator.Screen import ua.acclorite.book_story.presentation.navigator.Screen
import ua.acclorite.book_story.presentation.navigator.StackEvent import ua.acclorite.book_story.presentation.navigator.StackEvent
import ua.acclorite.book_story.presentation.settings.SettingsEvent
import ua.acclorite.book_story.presentation.settings.SettingsModel
import ua.acclorite.book_story.ui.common.helpers.LocalActivity import ua.acclorite.book_story.ui.common.helpers.LocalActivity
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
import ua.acclorite.book_story.ui.navigator.LocalNavigator import ua.acclorite.book_story.ui.navigator.LocalNavigator
import ua.acclorite.book_story.ui.start.StartContent import ua.acclorite.book_story.ui.start.StartContent
@ -51,22 +51,20 @@ object StartScreen : Screen, Parcelable {
@Composable @Composable
override fun Content() { override fun Content() {
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val mainModel = hiltViewModel<MainModel>() val settingsModel = hiltViewModel<SettingsModel>()
val settings = LocalSettings.current
val mainState = mainModel.state.collectAsStateWithLifecycle()
val activity = LocalActivity.current val activity = LocalActivity.current
val currentPage = remember { mutableIntStateOf(0) } val currentPage = remember { mutableIntStateOf(0) }
val stackEvent = remember { mutableStateOf(StackEvent.Default) } val stackEvent = remember { mutableStateOf(StackEvent.Default) }
val languages = remember(mainState.value.language) { val languages = remember(settings.language.value) {
CoreData.languages.sortedBy { it.second }.map { CoreData.languages.sortedBy { it.second }.map {
ButtonItem( ButtonItem(
id = it.first, id = it.first,
title = it.second, title = it.second,
textStyle = TextStyle(), textStyle = TextStyle(),
selected = it.first == mainState.value.language selected = it.first == settings.language.lastValue
) )
}.sortedBy { it.title } }.sortedBy { it.title }
} }
@ -75,7 +73,7 @@ object StartScreen : Screen, Parcelable {
currentPage = currentPage.intValue, currentPage = currentPage.intValue,
stackEvent = stackEvent.value, stackEvent = stackEvent.value,
languages = languages, languages = languages,
changeLanguage = mainModel::onEvent, updateLanguage = { settingsModel.onEvent(SettingsEvent.OnUpdateLanguage(it)) },
navigateForward = { navigateForward = {
if (currentPage.intValue + 1 == 4) { if (currentPage.intValue + 1 == 4) {
return@StartContent return@StartContent
@ -98,7 +96,7 @@ object StartScreen : Screen, Parcelable {
saveInBackStack = false saveInBackStack = false
) )
BrowseScreen.refreshListChannel.trySend(Unit) BrowseScreen.refreshListChannel.trySend(Unit)
mainModel.onEvent(MainEvent.OnChangeShowStartScreen(false)) settings.showStartScreen.update(false)
}, },
navigateToHelp = { navigateToHelp = {
navigator.push( navigator.push(

View file

@ -18,7 +18,6 @@ import ua.acclorite.book_story.presentation.browse.BrowseEvent
import ua.acclorite.book_story.presentation.browse.model.BrowseLayout import ua.acclorite.book_story.presentation.browse.model.BrowseLayout
import ua.acclorite.book_story.presentation.browse.model.SelectableFile import ua.acclorite.book_story.presentation.browse.model.SelectableFile
import ua.acclorite.book_story.presentation.library.model.SelectableNullableBook import ua.acclorite.book_story.presentation.library.model.SelectableNullableBook
import ua.acclorite.book_story.presentation.main.MainEvent
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
@ -60,7 +59,7 @@ fun BrowseContent(
dismissAddDialog: (BrowseEvent.OnDismissAddDialog) -> Unit, dismissAddDialog: (BrowseEvent.OnDismissAddDialog) -> Unit,
actionAddDialog: (BrowseEvent.OnActionAddDialog) -> Unit, actionAddDialog: (BrowseEvent.OnActionAddDialog) -> Unit,
selectAddDialog: (BrowseEvent.OnSelectAddDialog) -> Unit, selectAddDialog: (BrowseEvent.OnSelectAddDialog) -> Unit,
changePinnedPaths: (MainEvent.OnChangeBrowsePinnedPaths) -> Unit, updatePinnedPaths: (String) -> Unit,
navigateToLibrary: () -> Unit, navigateToLibrary: () -> Unit,
navigateToBrowseSettings: () -> Unit, navigateToBrowseSettings: () -> Unit,
) { ) {
@ -109,7 +108,7 @@ fun BrowseContent(
selectFile = selectFile, selectFile = selectFile,
showFilterBottomSheet = showFilterBottomSheet, showFilterBottomSheet = showFilterBottomSheet,
showAddDialog = showAddDialog, showAddDialog = showAddDialog,
changePinnedPaths = changePinnedPaths, updatePinnedPaths = updatePinnedPaths,
navigateToBrowseSettings = navigateToBrowseSettings navigateToBrowseSettings = navigateToBrowseSettings
) )

View file

@ -22,7 +22,6 @@ import androidx.compose.ui.focus.FocusRequester
import ua.acclorite.book_story.presentation.browse.BrowseEvent import ua.acclorite.book_story.presentation.browse.BrowseEvent
import ua.acclorite.book_story.presentation.browse.model.BrowseLayout import ua.acclorite.book_story.presentation.browse.model.BrowseLayout
import ua.acclorite.book_story.presentation.browse.model.SelectableFile import ua.acclorite.book_story.presentation.browse.model.SelectableFile
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.ui.theme.DefaultTransition import ua.acclorite.book_story.ui.theme.DefaultTransition
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@ -57,7 +56,7 @@ fun BrowseScaffold(
selectFile: (BrowseEvent.OnSelectFile) -> Unit, selectFile: (BrowseEvent.OnSelectFile) -> Unit,
showFilterBottomSheet: (BrowseEvent.OnShowFilterBottomSheet) -> Unit, showFilterBottomSheet: (BrowseEvent.OnShowFilterBottomSheet) -> Unit,
showAddDialog: (BrowseEvent.OnShowAddDialog) -> Unit, showAddDialog: (BrowseEvent.OnShowAddDialog) -> Unit,
changePinnedPaths: (MainEvent.OnChangeBrowsePinnedPaths) -> Unit, updatePinnedPaths: (String) -> Unit,
navigateToBrowseSettings: () -> Unit, navigateToBrowseSettings: () -> Unit,
) { ) {
Scaffold( Scaffold(
@ -107,11 +106,7 @@ fun BrowseScaffold(
header = header, header = header,
pinned = pinned, pinned = pinned,
pin = { pin = {
changePinnedPaths( updatePinnedPaths(header)
MainEvent.OnChangeBrowsePinnedPaths(
value = header
)
)
} }
) )
}, },

View file

@ -0,0 +1,23 @@
/*
* 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.common.helpers
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import ua.acclorite.book_story.data.settings.SettingsManager
val LocalSettings = staticCompositionLocalOf<SettingsManager> {
error("No settings provided")
}
@Composable
fun ProvideSettings(settings: SettingsManager, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalSettings provides settings) {
content()
}
}

View file

@ -6,10 +6,7 @@
package ua.acclorite.book_story.ui.help package ua.acclorite.book_story.ui.help
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button import androidx.compose.material3.Button
@ -19,29 +16,25 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.browse.BrowseScreen import ua.acclorite.book_story.presentation.browse.BrowseScreen
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.ui.common.components.common.StyledText import ua.acclorite.book_story.ui.common.components.common.StyledText
@Composable @Composable
fun HelpBottomBar( fun HelpBottomBar(
changeShowStartScreen: (MainEvent.OnChangeShowStartScreen) -> Unit, changeShowStartScreen: (Boolean) -> Unit,
navigateToBrowse: () -> Unit navigateToBrowse: () -> Unit
) { ) {
Column(Modifier.fillMaxWidth()) { Button(
Spacer(modifier = Modifier.height(18.dp)) modifier = Modifier
Button( .navigationBarsPadding()
modifier = Modifier .padding(top = 18.dp, bottom = 8.dp)
.navigationBarsPadding() .padding(horizontal = 18.dp)
.padding(bottom = 8.dp) .fillMaxWidth(),
.padding(horizontal = 18.dp) onClick = {
.fillMaxWidth(), BrowseScreen.refreshListChannel.trySend(Unit)
onClick = { changeShowStartScreen(false)
BrowseScreen.refreshListChannel.trySend(Unit) navigateToBrowse()
changeShowStartScreen(MainEvent.OnChangeShowStartScreen(false))
navigateToBrowse()
}
) {
StyledText(text = stringResource(id = R.string.done))
} }
) {
StyledText(text = stringResource(id = R.string.done))
} }
} }

View file

@ -10,7 +10,6 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import ua.acclorite.book_story.presentation.main.MainEvent
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -18,7 +17,7 @@ fun HelpContent(
fromStart: Boolean, fromStart: Boolean,
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
listState: LazyListState, listState: LazyListState,
changeShowStartScreen: (MainEvent.OnChangeShowStartScreen) -> Unit, changeShowStartScreen: (Boolean) -> Unit,
navigateToBrowse: () -> Unit, navigateToBrowse: () -> Unit,
navigateToStart: () -> Unit, navigateToStart: () -> Unit,
navigateBack: () -> Unit navigateBack: () -> Unit

View file

@ -18,7 +18,6 @@ import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import ua.acclorite.book_story.presentation.main.MainEvent
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -26,7 +25,7 @@ fun HelpScaffold(
fromStart: Boolean, fromStart: Boolean,
listState: LazyListState, listState: LazyListState,
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
changeShowStartScreen: (MainEvent.OnChangeShowStartScreen) -> Unit, changeShowStartScreen: (Boolean) -> Unit,
navigateToBrowse: () -> Unit, navigateToBrowse: () -> Unit,
navigateToStart: () -> Unit, navigateToStart: () -> Unit,
navigateBack: () -> Unit navigateBack: () -> Unit

View file

@ -14,7 +14,6 @@ import ua.acclorite.book_story.domain.model.library.CategorySort
import ua.acclorite.book_story.presentation.library.LibraryEvent import ua.acclorite.book_story.presentation.library.LibraryEvent
import ua.acclorite.book_story.presentation.library.LibraryScreen import ua.acclorite.book_story.presentation.library.LibraryScreen
import ua.acclorite.book_story.presentation.library.model.LibrarySortOrder import ua.acclorite.book_story.presentation.library.model.LibrarySortOrder
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.settings.SettingsEvent import ua.acclorite.book_story.presentation.settings.SettingsEvent
@Composable @Composable
@ -27,8 +26,8 @@ fun LibraryBottomSheet(
sortOrder: LibrarySortOrder, sortOrder: LibrarySortOrder,
sortOrderDescending: Boolean, sortOrderDescending: Boolean,
perCategorySort: Boolean, perCategorySort: Boolean,
changeLibrarySortOrder: (MainEvent.OnChangeLibrarySortOrder) -> Unit, changeSortOrder: (LibrarySortOrder) -> Unit,
changeLibrarySortOrderDescending: (MainEvent.OnChangeLibrarySortOrderDescending) -> Unit, changeSortOrderDescending: (Boolean) -> Unit,
updateCategorySort: (SettingsEvent.OnUpdateCategorySort) -> Unit, updateCategorySort: (SettingsEvent.OnUpdateCategorySort) -> Unit,
dismissBottomSheet: (LibraryEvent.OnDismissBottomSheet) -> Unit dismissBottomSheet: (LibraryEvent.OnDismissBottomSheet) -> Unit
) { ) {
@ -42,8 +41,8 @@ fun LibraryBottomSheet(
sortOrderDescending = sortOrderDescending, sortOrderDescending = sortOrderDescending,
categoriesSort = categoriesSort, categoriesSort = categoriesSort,
perCategorySort = perCategorySort, perCategorySort = perCategorySort,
changeLibrarySortOrder = changeLibrarySortOrder, changeSortOrder = changeSortOrder,
changeLibrarySortOrderDescending = changeLibrarySortOrderDescending, changeSortOrderDescending = changeSortOrderDescending,
updateCategorySort = updateCategorySort, updateCategorySort = updateCategorySort,
dismissBottomSheet = dismissBottomSheet dismissBottomSheet = dismissBottomSheet
) )

View file

@ -20,7 +20,6 @@ import ua.acclorite.book_story.presentation.library.model.LibraryLayout
import ua.acclorite.book_story.presentation.library.model.LibrarySortOrder import ua.acclorite.book_story.presentation.library.model.LibrarySortOrder
import ua.acclorite.book_story.presentation.library.model.LibraryTitlePosition import ua.acclorite.book_story.presentation.library.model.LibraryTitlePosition
import ua.acclorite.book_story.presentation.library.model.SelectableBook import ua.acclorite.book_story.presentation.library.model.SelectableBook
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.settings.SettingsEvent import ua.acclorite.book_story.presentation.settings.SettingsEvent
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@ -55,8 +54,8 @@ fun LibraryContent(
dialog: Dialog?, dialog: Dialog?,
bottomSheet: BottomSheet?, bottomSheet: BottomSheet?,
updateCategorySort: (SettingsEvent.OnUpdateCategorySort) -> Unit, updateCategorySort: (SettingsEvent.OnUpdateCategorySort) -> Unit,
changeLibrarySortOrder: (MainEvent.OnChangeLibrarySortOrder) -> Unit, changeSortOrder: (LibrarySortOrder) -> Unit,
changeLibrarySortOrderDescending: (MainEvent.OnChangeLibrarySortOrderDescending) -> Unit, changeSortOrderDescending: (Boolean) -> Unit,
selectBook: (LibraryEvent.OnSelectBook) -> Unit, selectBook: (LibraryEvent.OnSelectBook) -> Unit,
searchVisibility: (LibraryEvent.OnSearchVisibility) -> Unit, searchVisibility: (LibraryEvent.OnSearchVisibility) -> Unit,
requestFocus: (LibraryEvent.OnRequestFocus) -> Unit, requestFocus: (LibraryEvent.OnRequestFocus) -> Unit,
@ -95,8 +94,8 @@ fun LibraryContent(
sortOrder = sortOrder, sortOrder = sortOrder,
sortOrderDescending = sortOrderDescending, sortOrderDescending = sortOrderDescending,
perCategorySort = perCategorySort, perCategorySort = perCategorySort,
changeLibrarySortOrder = changeLibrarySortOrder, changeSortOrder = changeSortOrder,
changeLibrarySortOrderDescending = changeLibrarySortOrderDescending, changeSortOrderDescending = changeSortOrderDescending,
updateCategorySort = updateCategorySort, updateCategorySort = updateCategorySort,
dismissBottomSheet = dismissBottomSheet dismissBottomSheet = dismissBottomSheet
) )

View file

@ -26,7 +26,6 @@ import ua.acclorite.book_story.domain.model.library.Category
import ua.acclorite.book_story.domain.model.library.CategorySort import ua.acclorite.book_story.domain.model.library.CategorySort
import ua.acclorite.book_story.presentation.library.LibraryEvent import ua.acclorite.book_story.presentation.library.LibraryEvent
import ua.acclorite.book_story.presentation.library.model.LibrarySortOrder import ua.acclorite.book_story.presentation.library.model.LibrarySortOrder
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.settings.SettingsEvent import ua.acclorite.book_story.presentation.settings.SettingsEvent
import ua.acclorite.book_story.ui.common.components.common.LazyColumnWithScrollbar import ua.acclorite.book_story.ui.common.components.common.LazyColumnWithScrollbar
import ua.acclorite.book_story.ui.common.components.modal_bottom_sheet.ModalBottomSheet import ua.acclorite.book_story.ui.common.components.modal_bottom_sheet.ModalBottomSheet
@ -45,8 +44,8 @@ fun LibraryFilterBottomSheet(
sortOrderDescending: Boolean, sortOrderDescending: Boolean,
categoriesSort: List<CategorySort>, categoriesSort: List<CategorySort>,
perCategorySort: Boolean, perCategorySort: Boolean,
changeLibrarySortOrder: (MainEvent.OnChangeLibrarySortOrder) -> Unit, changeSortOrder: (LibrarySortOrder) -> Unit,
changeLibrarySortOrderDescending: (MainEvent.OnChangeLibrarySortOrderDescending) -> Unit, changeSortOrderDescending: (Boolean) -> Unit,
updateCategorySort: (SettingsEvent.OnUpdateCategorySort) -> Unit, updateCategorySort: (SettingsEvent.OnUpdateCategorySort) -> Unit,
dismissBottomSheet: (LibraryEvent.OnDismissBottomSheet) -> Unit dismissBottomSheet: (LibraryEvent.OnDismissBottomSheet) -> Unit
) { ) {
@ -123,16 +122,8 @@ fun LibraryFilterBottomSheet(
) )
) )
} else { } else {
changeLibrarySortOrder( changeSortOrder(sortOrder)
MainEvent.OnChangeLibrarySortOrder( changeSortOrderDescending(sortOrderDescending)
sortOrder.name
)
)
changeLibrarySortOrderDescending(
MainEvent.OnChangeLibrarySortOrderDescending(
sortOrderDescending
)
)
} }
} }
) )

View file

@ -24,7 +24,6 @@ import ua.acclorite.book_story.core.ui.UIText
import ua.acclorite.book_story.domain.model.library.Book import ua.acclorite.book_story.domain.model.library.Book
import ua.acclorite.book_story.domain.model.reader.ReaderText import ua.acclorite.book_story.domain.model.reader.ReaderText
import ua.acclorite.book_story.domain.model.reader.ReaderText.Chapter import ua.acclorite.book_story.domain.model.reader.ReaderText.Chapter
import ua.acclorite.book_story.presentation.main.model.HorizontalAlignment
import ua.acclorite.book_story.presentation.reader.ReaderEvent import ua.acclorite.book_story.presentation.reader.ReaderEvent
import ua.acclorite.book_story.presentation.reader.model.Checkpoint import ua.acclorite.book_story.presentation.reader.model.Checkpoint
import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness
@ -32,6 +31,7 @@ import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture
import ua.acclorite.book_story.presentation.reader.model.ReaderTextAlignment import ua.acclorite.book_story.presentation.reader.model.ReaderTextAlignment
import ua.acclorite.book_story.presentation.settings.SettingsEvent import ua.acclorite.book_story.presentation.settings.SettingsEvent
import ua.acclorite.book_story.ui.reader.model.FontWithName import ua.acclorite.book_story.ui.reader.model.FontWithName
import ua.acclorite.book_story.ui.theme.model.HorizontalAlignment
@Composable @Composable
fun ReaderContent( fun ReaderContent(

View file

@ -34,7 +34,6 @@ import androidx.compose.ui.unit.coerceAtLeast
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.reader.ReaderText import ua.acclorite.book_story.domain.model.reader.ReaderText
import ua.acclorite.book_story.presentation.main.model.HorizontalAlignment
import ua.acclorite.book_story.presentation.reader.ReaderEvent import ua.acclorite.book_story.presentation.reader.ReaderEvent
import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness
import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture
@ -47,6 +46,7 @@ import ua.acclorite.book_story.ui.common.helpers.LocalActivity
import ua.acclorite.book_story.ui.common.helpers.noRippleClickable import ua.acclorite.book_story.ui.common.helpers.noRippleClickable
import ua.acclorite.book_story.ui.common.helpers.showToast import ua.acclorite.book_story.ui.common.helpers.showToast
import ua.acclorite.book_story.ui.reader.model.FontWithName import ua.acclorite.book_story.ui.reader.model.FontWithName
import ua.acclorite.book_story.ui.theme.model.HorizontalAlignment
@Composable @Composable
fun ReaderLayout( fun ReaderLayout(

View file

@ -17,11 +17,11 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import ua.acclorite.book_story.domain.model.reader.ReaderText import ua.acclorite.book_story.domain.model.reader.ReaderText
import ua.acclorite.book_story.presentation.main.model.HorizontalAlignment
import ua.acclorite.book_story.presentation.reader.ReaderEvent import ua.acclorite.book_story.presentation.reader.ReaderEvent
import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness
import ua.acclorite.book_story.presentation.reader.model.ReaderTextAlignment import ua.acclorite.book_story.presentation.reader.model.ReaderTextAlignment
import ua.acclorite.book_story.ui.reader.model.FontWithName import ua.acclorite.book_story.ui.reader.model.FontWithName
import ua.acclorite.book_story.ui.theme.model.HorizontalAlignment
@Composable @Composable
fun LazyItemScope.ReaderLayoutText( fun LazyItemScope.ReaderLayoutText(

View file

@ -19,7 +19,7 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import ua.acclorite.book_story.domain.model.reader.ReaderText import ua.acclorite.book_story.domain.model.reader.ReaderText
import ua.acclorite.book_story.presentation.main.model.HorizontalAlignment import ua.acclorite.book_story.ui.theme.model.HorizontalAlignment
@Composable @Composable
fun LazyItemScope.ReaderLayoutTextImage( fun LazyItemScope.ReaderLayoutTextImage(

View file

@ -16,8 +16,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import ua.acclorite.book_story.presentation.main.model.HorizontalAlignment
import ua.acclorite.book_story.ui.common.components.common.StyledText import ua.acclorite.book_story.ui.common.components.common.StyledText
import ua.acclorite.book_story.ui.theme.model.HorizontalAlignment
@Composable @Composable
fun ReaderProgressBar( fun ReaderProgressBar(

View file

@ -29,7 +29,6 @@ import androidx.compose.ui.unit.TextUnit
import ua.acclorite.book_story.domain.model.library.Book import ua.acclorite.book_story.domain.model.library.Book
import ua.acclorite.book_story.domain.model.reader.ReaderText import ua.acclorite.book_story.domain.model.reader.ReaderText
import ua.acclorite.book_story.domain.model.reader.ReaderText.Chapter import ua.acclorite.book_story.domain.model.reader.ReaderText.Chapter
import ua.acclorite.book_story.presentation.main.model.HorizontalAlignment
import ua.acclorite.book_story.presentation.reader.ReaderEvent import ua.acclorite.book_story.presentation.reader.ReaderEvent
import ua.acclorite.book_story.presentation.reader.model.Checkpoint import ua.acclorite.book_story.presentation.reader.model.Checkpoint
import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness
@ -38,6 +37,7 @@ import ua.acclorite.book_story.presentation.reader.model.ReaderTextAlignment
import ua.acclorite.book_story.presentation.settings.SettingsEvent import ua.acclorite.book_story.presentation.settings.SettingsEvent
import ua.acclorite.book_story.ui.common.components.common.AnimatedVisibility import ua.acclorite.book_story.ui.common.components.common.AnimatedVisibility
import ua.acclorite.book_story.ui.reader.model.FontWithName import ua.acclorite.book_story.ui.reader.model.FontWithName
import ua.acclorite.book_story.ui.theme.model.HorizontalAlignment
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable @Composable

View file

@ -8,28 +8,20 @@ package ua.acclorite.book_story.ui.settings.appearance.colors.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun FastColorPresetChangeOption() { fun FastColorPresetChangeOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.fastColorPresetChange, selected = settings.fastColorPresetChange.value,
title = stringResource(id = R.string.fast_color_preset_change_option), title = stringResource(id = R.string.fast_color_preset_change_option),
description = stringResource(id = R.string.fast_color_preset_change_option_desc), description = stringResource(id = R.string.fast_color_preset_change_option_desc),
onClick = { onClick = {
mainModel.onEvent( settings.fastColorPresetChange.update(!settings.fastColorPresetChange.lastValue)
MainEvent.OnChangeFastColorPresetChange(
!state.value.fastColorPresetChange
)
)
} }
) )
} }

View file

@ -9,36 +9,26 @@ package ua.acclorite.book_story.ui.settings.appearance.theme_preferences.compone
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.main.model.isDark
import ua.acclorite.book_story.presentation.main.model.isPureDark
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun AbsoluteDarkOption() { fun AbsoluteDarkOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
val context = LocalContext.current val context = LocalContext.current
ExpandingTransition( ExpandingTransition(
visible = state.value.pureDark.isPureDark(context) visible = settings.pureDark.value.isPureDark(context)
&& state.value.darkTheme.isDark() && settings.darkTheme.value.isDark()
) { ) {
SwitchWithTitle( SwitchWithTitle(
selected = state.value.absoluteDark, selected = settings.absoluteDark.value,
title = stringResource(id = R.string.absolute_dark_option), title = stringResource(id = R.string.absolute_dark_option),
description = stringResource(id = R.string.absolute_dark_option_desc), description = stringResource(id = R.string.absolute_dark_option_desc),
onClick = { onClick = {
mainModel.onEvent( settings.absoluteDark.update(!settings.absoluteDark.lastValue)
MainEvent.OnChangeAbsoluteDark(
!state.value.absoluteDark
)
)
} }
) )
} }

View file

@ -39,24 +39,18 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.main.model.ThemeContrast
import ua.acclorite.book_story.presentation.main.model.isDark
import ua.acclorite.book_story.presentation.main.model.isPureDark
import ua.acclorite.book_story.ui.common.components.common.AnimatedVisibility import ua.acclorite.book_story.ui.common.components.common.AnimatedVisibility
import ua.acclorite.book_story.ui.common.components.common.StyledText import ua.acclorite.book_story.ui.common.components.common.StyledText
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.settings.components.SettingsSubcategoryTitle import ua.acclorite.book_story.ui.settings.components.SettingsSubcategoryTitle
import ua.acclorite.book_story.ui.theme.Theme
import ua.acclorite.book_story.ui.theme.animatedColorScheme import ua.acclorite.book_story.ui.theme.animatedColorScheme
import ua.acclorite.book_story.ui.theme.model.Theme
import ua.acclorite.book_story.ui.theme.model.ThemeContrast
@Composable @Composable
fun AppThemeOption() { fun AppThemeOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
Column( Column(
Modifier Modifier
@ -76,15 +70,15 @@ fun AppThemeOption() {
items( items(
Theme.entries(), Theme.entries(),
key = { theme -> theme.name } key = { theme -> theme.name }
) { theme -> ) { themeEntry ->
AppThemeOptionItem( AppThemeOptionItem(
theme = theme, theme = themeEntry,
darkTheme = state.value.darkTheme.isDark(), darkTheme = settings.darkTheme.value.isDark(),
themeContrast = state.value.themeContrast, themeContrast = settings.themeContrast.value,
isPureDark = state.value.pureDark.isPureDark(context = LocalContext.current), isPureDark = settings.pureDark.value.isPureDark(context = LocalContext.current),
selected = state.value.theme == theme selected = settings.theme.value == themeEntry
) { ) {
mainModel.onEvent(MainEvent.OnChangeTheme(theme.name)) settings.theme.update(themeEntry)
} }
} }
} }

View file

@ -9,35 +9,31 @@ package ua.acclorite.book_story.ui.settings.appearance.theme_preferences.compone
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.main.model.DarkTheme
import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
import ua.acclorite.book_story.ui.theme.model.DarkTheme
@Composable @Composable
fun DarkThemeOption() { fun DarkThemeOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SegmentedButtonWithTitle( SegmentedButtonWithTitle(
title = stringResource(id = R.string.dark_theme_option), title = stringResource(id = R.string.dark_theme_option),
buttons = DarkTheme.entries.map { buttons = DarkTheme.entries.map {
ButtonItem( ButtonItem(
it.toString(), it.name,
title = when (it) { title = when (it) {
DarkTheme.OFF -> stringResource(id = R.string.dark_theme_off) DarkTheme.OFF -> stringResource(id = R.string.dark_theme_off)
DarkTheme.ON -> stringResource(id = R.string.dark_theme_on) DarkTheme.ON -> stringResource(id = R.string.dark_theme_on)
DarkTheme.FOLLOW_SYSTEM -> stringResource(id = R.string.dark_theme_follow_system) DarkTheme.FOLLOW_SYSTEM -> stringResource(id = R.string.dark_theme_follow_system)
}, },
textStyle = MaterialTheme.typography.labelLarge, textStyle = MaterialTheme.typography.labelLarge,
selected = it == state.value.darkTheme selected = it == settings.darkTheme.value
) )
} }
) { ) {
mainModel.onEvent(MainEvent.OnChangeDarkTheme(it.id)) settings.darkTheme.update(DarkTheme.valueOf(it.id))
} }
} }

View file

@ -9,23 +9,18 @@ package ua.acclorite.book_story.ui.settings.appearance.theme_preferences.compone
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.main.model.PureDark
import ua.acclorite.book_story.presentation.main.model.isDark
import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
import ua.acclorite.book_story.ui.theme.model.PureDark
@Composable @Composable
fun PureDarkOption() { fun PureDarkOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition(visible = state.value.darkTheme.isDark()) { ExpandingTransition(visible = settings.darkTheme.value.isDark()) {
SegmentedButtonWithTitle( SegmentedButtonWithTitle(
title = stringResource(id = R.string.pure_dark_option), title = stringResource(id = R.string.pure_dark_option),
buttons = PureDark.entries.map { buttons = PureDark.entries.map {
@ -37,15 +32,11 @@ fun PureDarkOption() {
PureDark.SAVER -> stringResource(id = R.string.pure_dark_power_saver) PureDark.SAVER -> stringResource(id = R.string.pure_dark_power_saver)
}, },
textStyle = MaterialTheme.typography.labelLarge, textStyle = MaterialTheme.typography.labelLarge,
selected = it == state.value.pureDark selected = it == settings.pureDark.value
) )
} }
) { ) {
mainModel.onEvent( settings.pureDark.update(PureDark.valueOf(it.id))
MainEvent.OnChangePureDark(
it.id
)
)
} }
} }
} }

View file

@ -13,41 +13,35 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.main.model.ThemeContrast
import ua.acclorite.book_story.presentation.main.model.isDark
import ua.acclorite.book_story.presentation.main.model.isPureDark
import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
import ua.acclorite.book_story.ui.theme.BookStoryTheme import ua.acclorite.book_story.ui.theme.BookStoryTheme
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
import ua.acclorite.book_story.ui.theme.model.ThemeContrast
@Composable @Composable
fun ThemeContrastOption() { fun ThemeContrastOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
val themeContrastTheme = remember { mutableStateOf(state.value.theme) } val themeContrastTheme = remember { mutableStateOf(settings.theme.lastValue) }
LaunchedEffect(state.value.theme) { LaunchedEffect(settings.theme.value) {
if (state.value.theme.hasThemeContrast) { if (settings.theme.lastValue.hasThemeContrast) {
themeContrastTheme.value = state.value.theme themeContrastTheme.value = settings.theme.lastValue
} }
} }
BookStoryTheme( BookStoryTheme(
theme = themeContrastTheme.value, theme = themeContrastTheme.value,
isDark = state.value.darkTheme.isDark(), isDark = settings.darkTheme.value.isDark(),
isPureDark = state.value.pureDark.isPureDark(context = LocalContext.current), isPureDark = settings.pureDark.value.isPureDark(context = LocalContext.current),
themeContrast = state.value.themeContrast themeContrast = settings.themeContrast.value
) { ) {
ExpandingTransition(visible = state.value.theme.hasThemeContrast) { ExpandingTransition(visible = settings.theme.value.hasThemeContrast) {
SegmentedButtonWithTitle( SegmentedButtonWithTitle(
title = stringResource(id = R.string.theme_contrast_option), title = stringResource(id = R.string.theme_contrast_option),
enabled = state.value.theme.hasThemeContrast, enabled = settings.theme.value.hasThemeContrast,
buttons = ThemeContrast.entries.map { buttons = ThemeContrast.entries.map {
ButtonItem( ButtonItem(
id = it.toString(), id = it.toString(),
@ -57,15 +51,11 @@ fun ThemeContrastOption() {
ThemeContrast.HIGH -> stringResource(id = R.string.theme_contrast_high) ThemeContrast.HIGH -> stringResource(id = R.string.theme_contrast_high)
}, },
textStyle = MaterialTheme.typography.labelLarge, textStyle = MaterialTheme.typography.labelLarge,
selected = it == state.value.themeContrast selected = it == settings.themeContrast.value
) )
} }
) { ) {
mainModel.onEvent( settings.themeContrast.update(ThemeContrast.valueOf(it.id))
MainEvent.OnChangeThemeContrast(
it.id
)
)
} }
} }
} }

View file

@ -8,34 +8,28 @@ package ua.acclorite.book_story.ui.settings.browse.display.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.browse.model.BrowseLayout import ua.acclorite.book_story.presentation.browse.model.BrowseLayout
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun BrowseGridSizeOption() { fun BrowseGridSizeOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition(visible = state.value.browseLayout == BrowseLayout.GRID) { ExpandingTransition(visible = settings.browseLayout.value == BrowseLayout.GRID) {
SliderWithTitle( SliderWithTitle(
value = state.value.browseGridSize value = settings.browseGridSize.value
to " ${stringResource(R.string.grid_size_per_row)}", to " ${stringResource(R.string.grid_size_per_row)}",
valuePlaceholder = stringResource(id = R.string.grid_size_auto), valuePlaceholder = stringResource(id = R.string.grid_size_auto),
showPlaceholder = state.value.browseAutoGridSize, showPlaceholder = settings.browseAutoGridSize.value,
fromValue = 0, fromValue = 0,
toValue = 10, toValue = 10,
title = stringResource(id = R.string.grid_size_option), title = stringResource(id = R.string.grid_size_option),
onValueChange = { onValueChange = {
mainModel.onEvent(MainEvent.OnChangeBrowseAutoGridSize(it == 0)) settings.browseAutoGridSize.update(it == 0)
mainModel.onEvent( settings.browseGridSize.update(it)
MainEvent.OnChangeBrowseGridSize(it)
)
} }
) )
} }

View file

@ -9,19 +9,15 @@ package ua.acclorite.book_story.ui.settings.browse.display.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.browse.model.BrowseLayout import ua.acclorite.book_story.presentation.browse.model.BrowseLayout
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
@Composable @Composable
fun BrowseLayoutOption() { fun BrowseLayoutOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SegmentedButtonWithTitle( SegmentedButtonWithTitle(
title = stringResource(id = R.string.layout_option), title = stringResource(id = R.string.layout_option),
@ -33,14 +29,10 @@ fun BrowseLayoutOption() {
BrowseLayout.GRID -> stringResource(id = R.string.layout_grid) BrowseLayout.GRID -> stringResource(id = R.string.layout_grid)
}, },
MaterialTheme.typography.labelLarge, MaterialTheme.typography.labelLarge,
it == state.value.browseLayout it == settings.browseLayout.value
) )
} }
) { ) {
mainModel.onEvent( settings.browseLayout.update(BrowseLayout.valueOf(it.id))
MainEvent.OnChangeBrowseLayout(
it.id
)
)
} }
} }

View file

@ -23,26 +23,21 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
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.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.core.data.ExtensionsData import ua.acclorite.book_story.core.data.ExtensionsData
import ua.acclorite.book_story.presentation.main.MainEvent import ua.acclorite.book_story.core.helpers.toggle
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.common.StyledText import ua.acclorite.book_story.ui.common.components.common.StyledText
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
fun LazyListScope.BrowseFilterOption() { fun LazyListScope.BrowseFilterOption() {
items(ExtensionsData.fileExtensions, key = { it }) { items(ExtensionsData.fileExtensions, key = { it }) {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
BrowseFilterOptionItem( BrowseFilterOptionItem(
item = it, item = it,
isSelected = state.value.browseIncludedFilterItems.any { item -> isSelected = settings.browseIncludedFilterItems.value.any { item -> item == it }
item == it
}
) { ) {
mainModel.onEvent( settings.browseIncludedFilterItems.update(
MainEvent.OnChangeBrowseIncludedFilterItem(it) settings.browseIncludedFilterItems.lastValue.toggle(it)
) )
} }
} }

View file

@ -28,33 +28,25 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.browse.model.BrowseSortOrder import ua.acclorite.book_story.presentation.browse.model.BrowseSortOrder
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.common.StyledText import ua.acclorite.book_story.ui.common.components.common.StyledText
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
fun LazyListScope.BrowseSortOption() { fun LazyListScope.BrowseSortOption() {
items(BrowseSortOrder.entries, key = { it.name }) { items(BrowseSortOrder.entries, key = { it.name }) {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
BrowseSortOptionItem( BrowseSortOptionItem(
item = it, item = it,
isSelected = state.value.browseSortOrder == it, isSelected = settings.browseSortOrder.value == it,
isDescending = state.value.browseSortOrderDescending isDescending = settings.browseSortOrderDescending.value
) { ) {
if (state.value.browseSortOrder == it) { if (settings.browseSortOrder.lastValue == it) {
mainModel.onEvent( settings.browseSortOrderDescending.update(!settings.browseSortOrderDescending.lastValue)
MainEvent.OnChangeBrowseSortOrderDescending(
!state.value.browseSortOrderDescending
)
)
} else { } else {
mainModel.onEvent(MainEvent.OnChangeBrowseSortOrderDescending(true)) settings.browseSortOrderDescending.update(true)
mainModel.onEvent(MainEvent.OnChangeBrowseSortOrder(it.name)) settings.browseSortOrder.update(it)
} }
} }
} }

View file

@ -10,18 +10,18 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.core.data.CoreData import ua.acclorite.book_story.core.data.CoreData
import ua.acclorite.book_story.presentation.main.MainEvent import ua.acclorite.book_story.presentation.settings.SettingsEvent
import ua.acclorite.book_story.presentation.main.MainModel import ua.acclorite.book_story.presentation.settings.SettingsModel
import ua.acclorite.book_story.ui.common.components.settings.ChipsWithTitle import ua.acclorite.book_story.ui.common.components.settings.ChipsWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
@Composable @Composable
fun AppLanguageOption() { fun AppLanguageOption() {
val mainModel = hiltViewModel<MainModel>() val settingsModel = hiltViewModel<SettingsModel>()
val state = mainModel.state.collectAsStateWithLifecycle() val settings = LocalSettings.current
ChipsWithTitle( ChipsWithTitle(
title = stringResource(id = R.string.language_option), title = stringResource(id = R.string.language_option),
@ -30,10 +30,10 @@ fun AppLanguageOption() {
it.first, it.first,
it.second, it.second,
MaterialTheme.typography.labelLarge, MaterialTheme.typography.labelLarge,
it.first == state.value.language it.first == settings.language.value
) )
}.sortedBy { it.title } }
) { ) {
mainModel.onEvent(MainEvent.OnChangeLanguage(it.id)) settingsModel.onEvent(SettingsEvent.OnUpdateLanguage(it.id))
} }
} }

View file

@ -8,27 +8,19 @@ package ua.acclorite.book_story.ui.settings.general.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun DoublePressExitOption() { fun DoublePressExitOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.doublePressExit, selected = settings.doublePressExit.value,
title = stringResource(id = R.string.double_press_exit_option), title = stringResource(id = R.string.double_press_exit_option),
description = stringResource(id = R.string.double_press_exit_option_desc) description = stringResource(id = R.string.double_press_exit_option_desc)
) { ) {
mainModel.onEvent( settings.doublePressExit.update(!settings.doublePressExit.lastValue)
MainEvent.OnChangeDoublePressExit(
!state.value.doublePressExit
)
)
} }
} }

View file

@ -8,34 +8,28 @@ package ua.acclorite.book_story.ui.settings.library.display.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.library.model.LibraryLayout import ua.acclorite.book_story.presentation.library.model.LibraryLayout
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun LibraryGridSizeOption() { fun LibraryGridSizeOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition(visible = state.value.libraryLayout == LibraryLayout.GRID) { ExpandingTransition(visible = settings.libraryLayout.value == LibraryLayout.GRID) {
SliderWithTitle( SliderWithTitle(
value = state.value.libraryGridSize value = settings.libraryGridSize.value
to " ${stringResource(R.string.grid_size_per_row)}", to " ${stringResource(R.string.grid_size_per_row)}",
valuePlaceholder = stringResource(id = R.string.grid_size_auto), valuePlaceholder = stringResource(id = R.string.grid_size_auto),
showPlaceholder = state.value.libraryAutoGridSize, showPlaceholder = settings.libraryAutoGridSize.value,
fromValue = 0, fromValue = 0,
toValue = 10, toValue = 10,
title = stringResource(id = R.string.grid_size_option), title = stringResource(id = R.string.grid_size_option),
onValueChange = { onValueChange = {
mainModel.onEvent(MainEvent.OnChangeLibraryAutoGridSize(it == 0)) settings.libraryAutoGridSize.update(it == 0)
mainModel.onEvent( settings.libraryGridSize.update(it)
MainEvent.OnChangeLibraryGridSize(it)
)
} }
) )
} }

View file

@ -9,19 +9,15 @@ package ua.acclorite.book_story.ui.settings.library.display.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.library.model.LibraryLayout import ua.acclorite.book_story.presentation.library.model.LibraryLayout
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
@Composable @Composable
fun LibraryLayoutOption() { fun LibraryLayoutOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SegmentedButtonWithTitle( SegmentedButtonWithTitle(
title = stringResource(id = R.string.layout_option), title = stringResource(id = R.string.layout_option),
@ -33,14 +29,10 @@ fun LibraryLayoutOption() {
LibraryLayout.GRID -> stringResource(id = R.string.layout_grid) LibraryLayout.GRID -> stringResource(id = R.string.layout_grid)
}, },
MaterialTheme.typography.labelLarge, MaterialTheme.typography.labelLarge,
it == state.value.libraryLayout it == settings.libraryLayout.value
) )
} }
) { ) {
mainModel.onEvent( settings.libraryLayout.update(LibraryLayout.valueOf(it.id))
MainEvent.OnChangeLibraryLayout(
it.id
)
)
} }
} }

View file

@ -8,27 +8,19 @@ package ua.acclorite.book_story.ui.settings.library.display.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun LibraryReadButtonOption() { fun LibraryReadButtonOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.libraryReadButton, selected = settings.libraryShowReadButton.value,
title = stringResource(id = R.string.read_button_option), title = stringResource(id = R.string.read_button_option),
onClick = { onClick = {
mainModel.onEvent( settings.libraryShowReadButton.update(!settings.libraryShowReadButton.lastValue)
MainEvent.OnChangeLibraryReadButton(
!state.value.libraryReadButton
)
)
} }
) )
} }

View file

@ -8,27 +8,19 @@ package ua.acclorite.book_story.ui.settings.library.display.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun LibraryShowProgressOption() { fun LibraryShowProgressOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.libraryShowProgress, selected = settings.libraryShowProgress.value,
title = stringResource(id = R.string.show_progress_option), title = stringResource(id = R.string.show_progress_option),
onClick = { onClick = {
mainModel.onEvent( settings.libraryShowProgress.update(!settings.libraryShowProgress.lastValue)
MainEvent.OnChangeLibraryShowProgress(
!state.value.libraryShowProgress
)
)
} }
) )
} }

View file

@ -9,51 +9,35 @@ package ua.acclorite.book_story.ui.settings.library.display.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.library.model.LibraryLayout import ua.acclorite.book_story.presentation.library.model.LibraryLayout
import ua.acclorite.book_story.presentation.library.model.LibraryTitlePosition import ua.acclorite.book_story.presentation.library.model.LibraryTitlePosition
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.ChipsWithTitle import ua.acclorite.book_story.ui.common.components.settings.ChipsWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun LibraryTitlePositionOption() { fun LibraryTitlePositionOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition(visible = state.value.libraryLayout == LibraryLayout.GRID) { ExpandingTransition(visible = settings.libraryLayout.value == LibraryLayout.GRID) {
ChipsWithTitle( ChipsWithTitle(
title = stringResource(id = R.string.title_position_option), title = stringResource(id = R.string.title_position_option),
chips = LibraryTitlePosition.entries.map { chips = LibraryTitlePosition.entries.map {
ButtonItem( ButtonItem(
it.toString(), it.name,
when (it) { when (it) {
LibraryTitlePosition.OFF -> { LibraryTitlePosition.OFF -> stringResource(id = R.string.library_title_position_off)
stringResource(id = R.string.library_title_position_off) LibraryTitlePosition.BELOW -> stringResource(id = R.string.library_title_position_below)
} LibraryTitlePosition.INSIDE -> stringResource(id = R.string.library_title_position_inside)
LibraryTitlePosition.BELOW -> {
stringResource(id = R.string.library_title_position_below)
}
LibraryTitlePosition.INSIDE -> {
stringResource(id = R.string.library_title_position_inside)
}
}, },
MaterialTheme.typography.labelLarge, MaterialTheme.typography.labelLarge,
it == state.value.libraryTitlePosition it == settings.libraryTitlePosition.value
) )
} }
) { ) {
mainModel.onEvent( settings.libraryTitlePosition.update(LibraryTitlePosition.valueOf(it.id))
MainEvent.OnChangeLibraryTitlePosition(
it.id
)
)
} }
} }
} }

View file

@ -8,27 +8,19 @@ package ua.acclorite.book_story.ui.settings.library.sort.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun LibraryPerCategorySortOption() { fun LibraryPerCategorySortOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.libraryPerCategorySort, selected = settings.libraryPerCategorySort.value,
title = stringResource(id = R.string.per_category_sort_option), title = stringResource(id = R.string.per_category_sort_option),
onClick = { onClick = {
mainModel.onEvent( settings.libraryPerCategorySort.update(!settings.libraryPerCategorySort.lastValue)
MainEvent.OnChangeLibraryPerCategorySort(
!state.value.libraryPerCategorySort
)
)
} }
) )
} }

View file

@ -8,27 +8,19 @@ package ua.acclorite.book_story.ui.settings.library.tabs.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun LibraryAlwaysShowDefaultTabOption() { fun LibraryAlwaysShowDefaultTabOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.libraryAlwaysShowDefaultTab, selected = settings.libraryShowDefaultTab.value,
title = stringResource(id = R.string.always_show_default_tab_option), title = stringResource(id = R.string.always_show_default_tab_option),
onClick = { onClick = {
mainModel.onEvent( settings.libraryShowDefaultTab.update(!settings.libraryShowDefaultTab.lastValue)
MainEvent.OnChangeLibraryAlwaysShowDefaultTab(
!state.value.libraryAlwaysShowDefaultTab
)
)
} }
) )
} }

View file

@ -8,27 +8,19 @@ package ua.acclorite.book_story.ui.settings.library.tabs.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun LibraryShowBookCountOption() { fun LibraryShowBookCountOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.libraryShowBookCount, selected = settings.libraryShowBookCount.value,
title = stringResource(id = R.string.show_book_count_option), title = stringResource(id = R.string.show_book_count_option),
onClick = { onClick = {
mainModel.onEvent( settings.libraryShowBookCount.update(!settings.libraryShowBookCount.lastValue)
MainEvent.OnChangeLibraryShowBookCount(
!state.value.libraryShowBookCount
)
)
} }
) )
} }

View file

@ -8,27 +8,19 @@ package ua.acclorite.book_story.ui.settings.library.tabs.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun LibraryShowCategoryTabsOption() { fun LibraryShowCategoryTabsOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.libraryShowCategoryTabs, selected = settings.libraryShowCategoryTabs.value,
title = stringResource(id = R.string.show_category_tabs_option), title = stringResource(id = R.string.show_category_tabs_option),
onClick = { onClick = {
mainModel.onEvent( settings.libraryShowCategoryTabs.update(!settings.libraryShowCategoryTabs.lastValue)
MainEvent.OnChangeLibraryShowCategoryTabs(
!state.value.libraryShowCategoryTabs
)
)
} }
) )
} }

View file

@ -9,19 +9,15 @@ package ua.acclorite.book_story.ui.settings.reader.chapters.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.reader.model.ReaderTextAlignment import ua.acclorite.book_story.presentation.reader.model.ReaderTextAlignment
import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
@Composable @Composable
fun ChapterTitleAlignmentOption() { fun ChapterTitleAlignmentOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SegmentedButtonWithTitle( SegmentedButtonWithTitle(
title = stringResource(id = R.string.chapter_title_alignment_option), title = stringResource(id = R.string.chapter_title_alignment_option),
@ -35,15 +31,11 @@ fun ChapterTitleAlignmentOption() {
ReaderTextAlignment.END -> stringResource(id = R.string.alignment_end) ReaderTextAlignment.END -> stringResource(id = R.string.alignment_end)
}, },
textStyle = MaterialTheme.typography.labelLarge, textStyle = MaterialTheme.typography.labelLarge,
selected = it == state.value.chapterTitleAlignment selected = it == settings.chapterTitleAlignment.value
) )
}, },
onClick = { onClick = {
mainModel.onEvent( settings.chapterTitleAlignment.update(ReaderTextAlignment.valueOf(it.id))
MainEvent.OnChangeChapterTitleAlignment(
it.id
)
)
} }
) )
} }

View file

@ -8,48 +8,33 @@ package ua.acclorite.book_story.ui.settings.reader.font.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.ChipsWithTitle import ua.acclorite.book_story.ui.common.components.settings.ChipsWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
import ua.acclorite.book_story.ui.reader.data.ReaderData import ua.acclorite.book_story.ui.reader.data.ReaderData
@Composable @Composable
fun FontFamilyOption() { fun FontFamilyOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
val fontFamily = remember(state.value.fontFamily) {
ReaderData.fonts.run {
find {
it.id == state.value.fontFamily
} ?: get(0)
}
}
ChipsWithTitle( ChipsWithTitle(
title = stringResource(id = R.string.font_family_option), title = stringResource(id = R.string.font_family_option),
chips = ReaderData.fonts chips = ReaderData.fonts.map {
.map { ButtonItem(
ButtonItem( id = it.id,
id = it.id, title = it.fontName.asString(),
title = it.fontName.asString(), textStyle = MaterialTheme.typography.labelLarge.copy(
textStyle = MaterialTheme.typography.labelLarge.copy( fontFamily = it.font
fontFamily = it.font ),
), selected = it == settings.fontFamily.value
selected = it.id == fontFamily.id )
) },
},
onClick = { onClick = {
mainModel.onEvent( settings.fontFamily.update(
MainEvent.OnChangeFontFamily( ReaderData.fonts.find { font -> font.id == it.id }
it.id ?: ReaderData.fonts[0]
)
) )
} }
) )

View file

@ -8,27 +8,21 @@ package ua.acclorite.book_story.ui.settings.reader.font.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun FontSizeOption() { fun FontSizeOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SliderWithTitle( SliderWithTitle(
value = state.value.fontSize to "pt", value = settings.fontSize.value to "pt",
fromValue = 10, fromValue = 10,
toValue = 35, toValue = 35,
title = stringResource(id = R.string.font_size_option), title = stringResource(id = R.string.font_size_option),
onValueChange = { onValueChange = {
mainModel.onEvent( settings.fontSize.update(it)
MainEvent.OnChangeFontSize(it)
)
} }
) )
} }

View file

@ -8,30 +8,16 @@ package ua.acclorite.book_story.ui.settings.reader.font.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
import ua.acclorite.book_story.ui.reader.data.ReaderData
@Composable @Composable
fun FontStyleOption() { fun FontStyleOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
val fontFamily = remember(state.value.fontFamily) {
ReaderData.fonts.run {
find {
it.id == state.value.fontFamily
} ?: get(0)
}
}
SegmentedButtonWithTitle( SegmentedButtonWithTitle(
title = stringResource(id = R.string.font_style_option), title = stringResource(id = R.string.font_style_option),
@ -40,29 +26,27 @@ fun FontStyleOption() {
id = "normal", id = "normal",
title = stringResource(id = R.string.font_style_normal), title = stringResource(id = R.string.font_style_normal),
textStyle = MaterialTheme.typography.labelLarge.copy( textStyle = MaterialTheme.typography.labelLarge.copy(
fontFamily = fontFamily.font, fontFamily = settings.fontFamily.value.font,
fontStyle = FontStyle.Normal fontStyle = FontStyle.Normal
), ),
selected = !state.value.isItalic selected = !settings.italic.value
), ),
ButtonItem( ButtonItem(
id = "italic", id = "italic",
title = stringResource(id = R.string.font_style_italic), title = stringResource(id = R.string.font_style_italic),
textStyle = MaterialTheme.typography.labelLarge.copy( textStyle = MaterialTheme.typography.labelLarge.copy(
fontFamily = fontFamily.font, fontFamily = settings.fontFamily.value.font,
fontStyle = FontStyle.Italic fontStyle = FontStyle.Italic
), ),
selected = state.value.isItalic selected = settings.italic.value
) )
), ),
onClick = { onClick = {
mainModel.onEvent( settings.italic.update(
MainEvent.OnChangeFontStyle( when (it.id) {
when (it.id) { "italic" -> true
"italic" -> true else -> false
else -> false }
}
)
) )
} }
) )

View file

@ -8,30 +8,16 @@ package ua.acclorite.book_story.ui.settings.reader.font.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness import ua.acclorite.book_story.presentation.reader.model.ReaderFontThickness
import ua.acclorite.book_story.ui.common.components.settings.ChipsWithTitle import ua.acclorite.book_story.ui.common.components.settings.ChipsWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
import ua.acclorite.book_story.ui.reader.data.ReaderData
@Composable @Composable
fun FontThicknessOption() { fun FontThicknessOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
val fontFamily = remember(state.value.fontFamily) {
ReaderData.fonts.run {
find {
it.id == state.value.fontFamily
} ?: get(0)
}
}
ChipsWithTitle( ChipsWithTitle(
title = stringResource(id = R.string.font_thickness_option), title = stringResource(id = R.string.font_thickness_option),
@ -46,18 +32,14 @@ fun FontThicknessOption() {
ReaderFontThickness.MEDIUM -> stringResource(id = R.string.font_thickness_medium) ReaderFontThickness.MEDIUM -> stringResource(id = R.string.font_thickness_medium)
}, },
textStyle = MaterialTheme.typography.labelLarge.copy( textStyle = MaterialTheme.typography.labelLarge.copy(
fontFamily = fontFamily.font, fontFamily = settings.fontFamily.value.font,
fontWeight = it.thickness fontWeight = it.thickness
), ),
selected = it == state.value.fontThickness selected = it == settings.fontThickness.value
) )
}, },
onClick = { onClick = {
mainModel.onEvent( settings.fontThickness.update(ReaderFontThickness.valueOf(it.id))
MainEvent.OnChangeFontThickness(
it.id
)
)
} }
) )
} }

View file

@ -8,27 +8,21 @@ package ua.acclorite.book_story.ui.settings.reader.font.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun LetterSpacingOption() { fun LetterSpacingOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SliderWithTitle( SliderWithTitle(
value = state.value.letterSpacing to "pt", value = settings.letterSpacing.value to "pt",
fromValue = -8, fromValue = -8,
toValue = 16, toValue = 16,
title = stringResource(id = R.string.letter_spacing_option), title = stringResource(id = R.string.letter_spacing_option),
onValueChange = { onValueChange = {
mainModel.onEvent( settings.letterSpacing.update(it)
MainEvent.OnChangeLetterSpacing(it)
)
} }
) )
} }

View file

@ -9,42 +9,34 @@ package ua.acclorite.book_story.ui.settings.reader.images.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.main.model.HorizontalAlignment
import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
import ua.acclorite.book_story.ui.theme.model.HorizontalAlignment
@Composable @Composable
fun ImagesAlignmentOption() { fun ImagesAlignmentOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition(visible = state.value.images) { ExpandingTransition(visible = settings.images.value) {
SegmentedButtonWithTitle( SegmentedButtonWithTitle(
title = stringResource(id = R.string.images_alignment_option), title = stringResource(id = R.string.images_alignment_option),
buttons = HorizontalAlignment.entries.map { buttons = HorizontalAlignment.entries.map {
ButtonItem( ButtonItem(
id = it.toString(), id = it.name,
title = when (it) { title = when (it) {
HorizontalAlignment.START -> stringResource(id = R.string.alignment_start) HorizontalAlignment.START -> stringResource(id = R.string.alignment_start)
HorizontalAlignment.CENTER -> stringResource(id = R.string.alignment_center) HorizontalAlignment.CENTER -> stringResource(id = R.string.alignment_center)
HorizontalAlignment.END -> stringResource(id = R.string.alignment_end) HorizontalAlignment.END -> stringResource(id = R.string.alignment_end)
}, },
textStyle = MaterialTheme.typography.labelLarge, textStyle = MaterialTheme.typography.labelLarge,
selected = it == state.value.imagesAlignment selected = it == settings.imagesAlignment.value
) )
}, },
onClick = { onClick = {
mainModel.onEvent( settings.imagesAlignment.update(HorizontalAlignment.valueOf(it.id))
MainEvent.OnChangeImagesAlignment(
it.id
)
)
} }
) )
} }

View file

@ -8,30 +8,22 @@ package ua.acclorite.book_story.ui.settings.reader.images.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun ImagesCaptionsOption() { fun ImagesCaptionsOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition(visible = state.value.images) { ExpandingTransition(visible = settings.images.value) {
SwitchWithTitle( SwitchWithTitle(
selected = state.value.imagesCaptions, selected = settings.imagesCaptions.value,
title = stringResource(id = R.string.images_captions_option), title = stringResource(id = R.string.images_captions_option),
description = stringResource(id = R.string.images_captions_option_desc), description = stringResource(id = R.string.images_captions_option_desc),
onClick = { onClick = {
mainModel.onEvent( settings.imagesCaptions.update(!settings.imagesCaptions.lastValue)
MainEvent.OnChangeImagesCaptions(
!state.value.imagesCaptions
)
)
} }
) )
} }

View file

@ -9,54 +9,35 @@ package ua.acclorite.book_story.ui.settings.reader.images.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.reader.model.ReaderColorEffects import ua.acclorite.book_story.presentation.reader.model.ReaderColorEffects
import ua.acclorite.book_story.ui.common.components.settings.ChipsWithTitle import ua.acclorite.book_story.ui.common.components.settings.ChipsWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun ImagesColorEffectsOption() { fun ImagesColorEffectsOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition(visible = state.value.images) { ExpandingTransition(visible = settings.images.value) {
ChipsWithTitle( ChipsWithTitle(
title = stringResource(id = R.string.images_color_effects_option), title = stringResource(id = R.string.images_color_effects_option),
chips = ReaderColorEffects.entries.map { chips = ReaderColorEffects.entries.map {
ButtonItem( ButtonItem(
id = it.toString(), id = it.name,
title = when (it) { title = when (it) {
ReaderColorEffects.OFF -> { ReaderColorEffects.OFF -> stringResource(R.string.color_effects_off)
stringResource(R.string.color_effects_off) ReaderColorEffects.GRAYSCALE -> stringResource(R.string.color_effects_grayscale)
} ReaderColorEffects.FONT -> stringResource(R.string.color_effects_font)
ReaderColorEffects.BACKGROUND -> stringResource(R.string.color_effects_background)
ReaderColorEffects.GRAYSCALE -> {
stringResource(R.string.color_effects_grayscale)
}
ReaderColorEffects.FONT -> {
stringResource(R.string.color_effects_font)
}
ReaderColorEffects.BACKGROUND -> {
stringResource(R.string.color_effects_background)
}
}, },
textStyle = MaterialTheme.typography.labelLarge, textStyle = MaterialTheme.typography.labelLarge,
selected = it == state.value.imagesColorEffects selected = it == settings.imagesColorEffects.value
) )
}, },
onClick = { onClick = {
mainModel.onEvent( settings.imagesColorEffects.update(ReaderColorEffects.valueOf(it.id))
MainEvent.OnChangeImagesColorEffects(
it.id
)
)
} }
) )
} }

View file

@ -8,29 +8,23 @@ package ua.acclorite.book_story.ui.settings.reader.images.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun ImagesCornersRoundnessOption() { fun ImagesCornersRoundnessOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition(visible = state.value.images) { ExpandingTransition(visible = settings.images.value) {
SliderWithTitle( SliderWithTitle(
value = state.value.imagesCornersRoundness to "pt", value = settings.imagesCornersRoundness.value to "pt",
fromValue = 0, fromValue = 0,
toValue = 24, toValue = 24,
title = stringResource(id = R.string.images_corners_roundness_option), title = stringResource(id = R.string.images_corners_roundness_option),
onValueChange = { onValueChange = {
mainModel.onEvent( settings.imagesCornersRoundness.update(it)
MainEvent.OnChangeImagesCornersRoundness(it)
)
} }
) )
} }

View file

@ -8,28 +8,20 @@ package ua.acclorite.book_story.ui.settings.reader.images.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun ImagesOption() { fun ImagesOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.images, selected = settings.images.value,
title = stringResource(id = R.string.images_option), title = stringResource(id = R.string.images_option),
description = stringResource(id = R.string.images_option_desc), description = stringResource(id = R.string.images_option_desc),
onClick = { onClick = {
mainModel.onEvent( settings.images.update(!settings.images.lastValue)
MainEvent.OnChangeImages(
!state.value.images
)
)
} }
) )
} }

View file

@ -8,28 +8,22 @@ package ua.acclorite.book_story.ui.settings.reader.images.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun ImagesWidthOption() { fun ImagesWidthOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition(visible = state.value.images) { ExpandingTransition(visible = settings.images.value) {
SliderWithTitle( SliderWithTitle(
value = state.value.imagesWidth to "%", value = settings.imagesWidth.value to "%",
toValue = 100, toValue = 100,
title = stringResource(id = R.string.images_width_option), title = stringResource(id = R.string.images_width_option),
onValueChange = { onValueChange = {
mainModel.onEvent( settings.imagesWidth.update(it)
MainEvent.OnChangeImagesWidth(it)
)
} }
) )
} }

View file

@ -8,28 +8,20 @@ package ua.acclorite.book_story.ui.settings.reader.misc.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun FullscreenOption() { fun FullscreenOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.fullscreen, selected = settings.fullscreen.value,
title = stringResource(id = R.string.fullscreen_option), title = stringResource(id = R.string.fullscreen_option),
description = stringResource(id = R.string.fullscreen_option_desc), description = stringResource(id = R.string.fullscreen_option_desc),
onClick = { onClick = {
mainModel.onEvent( settings.fullscreen.update(!settings.fullscreen.lastValue)
MainEvent.OnChangeFullscreen(
!state.value.fullscreen
)
)
} }
) )
} }

View file

@ -8,24 +8,18 @@ package ua.acclorite.book_story.ui.settings.reader.misc.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun HideBarsOnFastScrollOption() { fun HideBarsOnFastScrollOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.hideBarsOnFastScroll, selected = settings.hideBarsOnFastScroll.value,
title = stringResource(id = R.string.hide_bars_on_fast_scroll_option) title = stringResource(id = R.string.hide_bars_on_fast_scroll_option)
) { ) {
mainModel.onEvent( settings.hideBarsOnFastScroll.update(!settings.hideBarsOnFastScroll.lastValue)
MainEvent.OnChangeHideBarsOnFastScroll(!state.value.hideBarsOnFastScroll)
)
} }
} }

View file

@ -8,27 +8,19 @@ package ua.acclorite.book_story.ui.settings.reader.misc.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun KeepScreenOnOption() { fun KeepScreenOnOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.keepScreenOn, selected = settings.keepScreenOn.value,
title = stringResource(id = R.string.keep_screen_on_option), title = stringResource(id = R.string.keep_screen_on_option),
onClick = { onClick = {
mainModel.onEvent( settings.keepScreenOn.update(!settings.keepScreenOn.lastValue)
MainEvent.OnChangeKeepScreenOn(
!state.value.keepScreenOn
)
)
} }
) )
} }

View file

@ -8,27 +8,21 @@ package ua.acclorite.book_story.ui.settings.reader.padding.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun BottomBarPaddingOption() { fun BottomBarPaddingOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SliderWithTitle( SliderWithTitle(
value = state.value.bottomBarPadding to "pt", value = settings.bottomBarPadding.value to "pt",
fromValue = 0, fromValue = 0,
toValue = 24, toValue = 24,
title = stringResource(id = R.string.bottom_bar_padding_option), title = stringResource(id = R.string.bottom_bar_padding_option),
onValueChange = { onValueChange = {
mainModel.onEvent( settings.bottomBarPadding.update(it)
MainEvent.OnChangeBottomBarPadding(it)
)
} }
) )
} }

View file

@ -8,28 +8,20 @@ package ua.acclorite.book_story.ui.settings.reader.padding.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun CutoutPaddingOption() { fun CutoutPaddingOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.cutoutPadding, selected = settings.cutoutPadding.value,
title = stringResource(id = R.string.cutout_padding_option), title = stringResource(id = R.string.cutout_padding_option),
description = stringResource(id = R.string.cutout_padding_option_desc), description = stringResource(id = R.string.cutout_padding_option_desc),
onClick = { onClick = {
mainModel.onEvent( settings.cutoutPadding.update(!settings.cutoutPadding.lastValue)
MainEvent.OnChangeCutoutPadding(
!state.value.cutoutPadding
)
)
} }
) )
} }

View file

@ -8,27 +8,21 @@ package ua.acclorite.book_story.ui.settings.reader.padding.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun SidePaddingOption() { fun SidePaddingOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SliderWithTitle( SliderWithTitle(
value = state.value.sidePadding to "pt", value = settings.sidePadding.value to "pt",
fromValue = 1, fromValue = 1,
toValue = 20, toValue = 20,
title = stringResource(id = R.string.side_padding_option), title = stringResource(id = R.string.side_padding_option),
onValueChange = { onValueChange = {
mainModel.onEvent( settings.sidePadding.update(it)
MainEvent.OnChangeSidePadding(it)
)
} }
) )
} }

View file

@ -8,27 +8,21 @@ package ua.acclorite.book_story.ui.settings.reader.padding.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun VerticalPaddingOption() { fun VerticalPaddingOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SliderWithTitle( SliderWithTitle(
value = state.value.verticalPadding to "pt", value = settings.verticalPadding.value to "pt",
fromValue = 0, fromValue = 0,
toValue = 24, toValue = 24,
title = stringResource(id = R.string.vertical_padding_option), title = stringResource(id = R.string.vertical_padding_option),
onValueChange = { onValueChange = {
mainModel.onEvent( settings.verticalPadding.update(it)
MainEvent.OnChangeVerticalPadding(it)
)
} }
) )
} }

View file

@ -9,42 +9,34 @@ package ua.acclorite.book_story.ui.settings.reader.progress.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.main.model.HorizontalAlignment
import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
import ua.acclorite.book_story.ui.theme.model.HorizontalAlignment
@Composable @Composable
fun ProgressBarAlignmentOption() { fun ProgressBarAlignmentOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition(visible = state.value.progressBar) { ExpandingTransition(visible = settings.progressBar.value) {
SegmentedButtonWithTitle( SegmentedButtonWithTitle(
title = stringResource(id = R.string.progress_bar_alignment_option), title = stringResource(id = R.string.progress_bar_alignment_option),
buttons = HorizontalAlignment.entries.map { buttons = HorizontalAlignment.entries.map {
ButtonItem( ButtonItem(
id = it.toString(), id = it.name,
title = when (it) { title = when (it) {
HorizontalAlignment.START -> stringResource(id = R.string.alignment_start) HorizontalAlignment.START -> stringResource(id = R.string.alignment_start)
HorizontalAlignment.CENTER -> stringResource(id = R.string.alignment_center) HorizontalAlignment.CENTER -> stringResource(id = R.string.alignment_center)
HorizontalAlignment.END -> stringResource(id = R.string.alignment_end) HorizontalAlignment.END -> stringResource(id = R.string.alignment_end)
}, },
textStyle = MaterialTheme.typography.labelLarge, textStyle = MaterialTheme.typography.labelLarge,
selected = it == state.value.progressBarAlignment selected = it == settings.progressBarAlignment.value
) )
}, },
onClick = { onClick = {
mainModel.onEvent( settings.progressBarAlignment.update(HorizontalAlignment.valueOf(it.id))
MainEvent.OnChangeProgressBarAlignment(
it.id
)
)
} }
) )
} }

View file

@ -8,29 +8,23 @@ package ua.acclorite.book_story.ui.settings.reader.progress.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun ProgressBarFontSizeOption() { fun ProgressBarFontSizeOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition(visible = state.value.progressBar) { ExpandingTransition(visible = settings.progressBar.value) {
SliderWithTitle( SliderWithTitle(
value = state.value.progressBarFontSize to "pt", value = settings.progressBarFontSize.value to "pt",
fromValue = 4, fromValue = 4,
toValue = 16, toValue = 16,
title = stringResource(id = R.string.progress_bar_font_size_option), title = stringResource(id = R.string.progress_bar_font_size_option),
onValueChange = { onValueChange = {
mainModel.onEvent( settings.progressBarFontSize.update(it)
MainEvent.OnChangeProgressBarFontSize(it)
)
} }
) )
} }

View file

@ -8,28 +8,20 @@ package ua.acclorite.book_story.ui.settings.reader.progress.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
@Composable @Composable
fun ProgressBarOption() { fun ProgressBarOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle( SwitchWithTitle(
selected = state.value.progressBar, selected = settings.progressBar.value,
title = stringResource(id = R.string.progress_bar_option), title = stringResource(id = R.string.progress_bar_option),
description = stringResource(id = R.string.progress_bar_option_desc), description = stringResource(id = R.string.progress_bar_option_desc),
onClick = { onClick = {
mainModel.onEvent( settings.progressBar.update(!settings.progressBar.lastValue)
MainEvent.OnChangeProgressBar(
!state.value.progressBar
)
)
} }
) )
} }

View file

@ -8,29 +8,23 @@ package ua.acclorite.book_story.ui.settings.reader.progress.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun ProgressBarPaddingOption() { fun ProgressBarPaddingOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition(visible = state.value.progressBar) { ExpandingTransition(visible = settings.progressBar.value) {
SliderWithTitle( SliderWithTitle(
value = state.value.progressBarPadding to "pt", value = settings.progressBarPadding.value to "pt",
fromValue = 1, fromValue = 1,
toValue = 12, toValue = 12,
title = stringResource(id = R.string.progress_bar_padding_option), title = stringResource(id = R.string.progress_bar_padding_option),
onValueChange = { onValueChange = {
mainModel.onEvent( settings.progressBarPadding.update(it)
MainEvent.OnChangeProgressBarPadding(it)
)
} }
) )
} }

View file

@ -9,39 +9,31 @@ package ua.acclorite.book_story.ui.settings.reader.progress.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.reader.model.ReaderProgressCount import ua.acclorite.book_story.presentation.reader.model.ReaderProgressCount
import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle import ua.acclorite.book_story.ui.common.components.settings.SegmentedButtonWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
@Composable @Composable
fun ProgressCountOption() { fun ProgressCountOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
SegmentedButtonWithTitle( SegmentedButtonWithTitle(
title = stringResource(id = R.string.progress_count_option), title = stringResource(id = R.string.progress_count_option),
buttons = ReaderProgressCount.entries.map { buttons = ReaderProgressCount.entries.map {
ButtonItem( ButtonItem(
id = it.toString(), id = it.name,
title = when (it) { title = when (it) {
ReaderProgressCount.PERCENTAGE -> stringResource(id = R.string.progress_count_percentage) ReaderProgressCount.PERCENTAGE -> stringResource(id = R.string.progress_count_percentage)
ReaderProgressCount.QUANTITY -> stringResource(id = R.string.progress_count_quantity) ReaderProgressCount.QUANTITY -> stringResource(id = R.string.progress_count_quantity)
}, },
textStyle = MaterialTheme.typography.labelLarge, textStyle = MaterialTheme.typography.labelLarge,
selected = it == state.value.progressCount selected = it == settings.progressCount.value
) )
}, },
onClick = { onClick = {
mainModel.onEvent( settings.progressCount.update(ReaderProgressCount.valueOf(it.id))
MainEvent.OnChangeProgressCount(
it.id
)
)
} }
) )
} }

View file

@ -8,36 +8,28 @@ package ua.acclorite.book_story.ui.settings.reader.reading_mode.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun HorizontalGestureAlphaAnimOption() { fun HorizontalGestureAlphaAnimOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition( ExpandingTransition(
visible = when (state.value.horizontalGesture) { visible = when (settings.horizontalGesture.value) {
ReaderHorizontalGesture.OFF -> false ReaderHorizontalGesture.OFF -> false
else -> true else -> true
} }
) { ) {
SwitchWithTitle( SwitchWithTitle(
selected = state.value.horizontalGestureAlphaAnim, selected = settings.horizontalGestureAlphaAnim.value,
title = stringResource(id = R.string.horizontal_gesture_alpha_anim_option), title = stringResource(id = R.string.horizontal_gesture_alpha_anim_option),
description = stringResource(id = R.string.horizontal_gesture_alpha_anim_option_desc), description = stringResource(id = R.string.horizontal_gesture_alpha_anim_option_desc),
onClick = { onClick = {
mainModel.onEvent( settings.horizontalGestureAlphaAnim.update(!settings.horizontalGestureAlphaAnim.lastValue)
MainEvent.OnChangeHorizontalGestureAlphaAnim(
!state.value.horizontalGestureAlphaAnim
)
)
} }
) )
} }

View file

@ -9,48 +9,32 @@ package ua.acclorite.book_story.ui.settings.reader.reading_mode.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture
import ua.acclorite.book_story.ui.common.components.settings.ChipsWithTitle import ua.acclorite.book_story.ui.common.components.settings.ChipsWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.model.ButtonItem import ua.acclorite.book_story.ui.common.model.ButtonItem
@Composable @Composable
fun HorizontalGestureOption() { fun HorizontalGestureOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ChipsWithTitle( ChipsWithTitle(
title = stringResource(id = R.string.horizontal_gesture_option), title = stringResource(id = R.string.horizontal_gesture_option),
chips = ReaderHorizontalGesture.entries.map { chips = ReaderHorizontalGesture.entries.map {
ButtonItem( ButtonItem(
id = it.toString(), id = it.name,
title = when (it) { title = when (it) {
ReaderHorizontalGesture.OFF -> { ReaderHorizontalGesture.OFF -> stringResource(R.string.horizontal_gesture_off)
stringResource(R.string.horizontal_gesture_off) ReaderHorizontalGesture.ON -> stringResource(R.string.horizontal_gesture_on)
} ReaderHorizontalGesture.INVERSE -> stringResource(R.string.horizontal_gesture_inverse)
ReaderHorizontalGesture.ON -> {
stringResource(R.string.horizontal_gesture_on)
}
ReaderHorizontalGesture.INVERSE -> {
stringResource(R.string.horizontal_gesture_inverse)
}
}, },
textStyle = MaterialTheme.typography.labelLarge, textStyle = MaterialTheme.typography.labelLarge,
selected = it == state.value.horizontalGesture selected = it == settings.horizontalGesture.value
) )
}, },
onClick = { onClick = {
mainModel.onEvent( settings.horizontalGesture.update(ReaderHorizontalGesture.valueOf(it.id))
MainEvent.OnChangeHorizontalGesture(
it.id
)
)
} }
) )
} }

View file

@ -8,36 +8,28 @@ package ua.acclorite.book_story.ui.settings.reader.reading_mode.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture
import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle import ua.acclorite.book_story.ui.common.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun HorizontalGesturePullAnimOption() { fun HorizontalGesturePullAnimOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition( ExpandingTransition(
visible = when (state.value.horizontalGesture) { visible = when (settings.horizontalGesture.value) {
ReaderHorizontalGesture.OFF -> false ReaderHorizontalGesture.OFF -> false
else -> true else -> true
} }
) { ) {
SwitchWithTitle( SwitchWithTitle(
selected = state.value.horizontalGesturePullAnim, selected = settings.horizontalGesturePullAnim.value,
title = stringResource(id = R.string.horizontal_gesture_pull_anim_option), title = stringResource(id = R.string.horizontal_gesture_pull_anim_option),
description = stringResource(id = R.string.horizontal_gesture_pull_anim_option_desc), description = stringResource(id = R.string.horizontal_gesture_pull_anim_option_desc),
onClick = { onClick = {
mainModel.onEvent( settings.horizontalGesturePullAnim.update(!settings.horizontalGesturePullAnim.lastValue)
MainEvent.OnChangeHorizontalGesturePullAnim(
!state.value.horizontalGesturePullAnim
)
)
} }
) )
} }

View file

@ -8,34 +8,28 @@ package ua.acclorite.book_story.ui.settings.reader.reading_mode.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.main.MainEvent
import ua.acclorite.book_story.presentation.main.MainModel
import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture import ua.acclorite.book_story.presentation.reader.model.ReaderHorizontalGesture
import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle import ua.acclorite.book_story.ui.common.components.settings.SliderWithTitle
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.theme.ExpandingTransition import ua.acclorite.book_story.ui.theme.ExpandingTransition
@Composable @Composable
fun HorizontalGestureScrollOption() { fun HorizontalGestureScrollOption() {
val mainModel = hiltViewModel<MainModel>() val settings = LocalSettings.current
val state = mainModel.state.collectAsStateWithLifecycle()
ExpandingTransition( ExpandingTransition(
visible = when (state.value.horizontalGesture) { visible = when (settings.horizontalGesture.value) {
ReaderHorizontalGesture.OFF -> false ReaderHorizontalGesture.OFF -> false
else -> true else -> true
} }
) { ) {
SliderWithTitle( SliderWithTitle(
value = state.value.horizontalGestureScroll to "%", value = settings.horizontalGestureScroll.value to "%",
toValue = 100, toValue = 100,
title = stringResource(id = R.string.horizontal_gesture_scroll_option), title = stringResource(id = R.string.horizontal_gesture_scroll_option),
onValueChange = { onValueChange = {
mainModel.onEvent( settings.horizontalGestureScroll.update(it)
MainEvent.OnChangeHorizontalGestureScroll(it)
)
} }
) )
} }

Some files were not shown because too many files have changed in this diff Show more