0.9.7 - Help Screen + Pure Dark (Oled) theme + Bug fixes
This commit is contained in:
parent
76e1ca3e3f
commit
513b286e07
63 changed files with 1915 additions and 487 deletions
|
|
@ -15,7 +15,7 @@ android {
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "0.9.6"
|
versionName = "0.9.7"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|
@ -66,7 +66,7 @@ dependencies {
|
||||||
implementation("androidx.activity:activity-compose:1.8.2")
|
implementation("androidx.activity:activity-compose:1.8.2")
|
||||||
|
|
||||||
// Compose BOM libraries
|
// Compose BOM libraries
|
||||||
implementation(platform("androidx.compose:compose-bom:2024.03.00"))
|
implementation(platform("androidx.compose:compose-bom:2024.04.00"))
|
||||||
implementation("androidx.compose.foundation:foundation")
|
implementation("androidx.compose.foundation:foundation")
|
||||||
implementation("androidx.compose.animation:animation")
|
implementation("androidx.compose.animation:animation")
|
||||||
implementation("androidx.compose.animation:animation-core")
|
implementation("androidx.compose.animation:animation-core")
|
||||||
|
|
@ -90,7 +90,7 @@ dependencies {
|
||||||
testImplementation("androidx.test.ext:truth:1.5.0")
|
testImplementation("androidx.test.ext:truth:1.5.0")
|
||||||
testImplementation("com.squareup.okhttp3:mockwebserver:latest.release")
|
testImplementation("com.squareup.okhttp3:mockwebserver:latest.release")
|
||||||
testImplementation("io.mockk:mockk:latest.release")
|
testImplementation("io.mockk:mockk:latest.release")
|
||||||
debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.4")
|
debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.5")
|
||||||
|
|
||||||
// Instrumentation tests
|
// Instrumentation tests
|
||||||
androidTestImplementation("com.google.dagger:hilt-android-testing:latest.release")
|
androidTestImplementation("com.google.dagger:hilt-android-testing:latest.release")
|
||||||
|
|
@ -109,7 +109,7 @@ dependencies {
|
||||||
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.2-alpha")
|
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.2-alpha")
|
||||||
|
|
||||||
// Dagger - Hilt
|
// Dagger - Hilt
|
||||||
implementation("com.google.dagger:hilt-android:2.51")
|
implementation("com.google.dagger:hilt-android:2.51.1")
|
||||||
ksp("com.google.dagger:hilt-android-compiler:2.51")
|
ksp("com.google.dagger:hilt-android-compiler:2.51")
|
||||||
implementation("com.google.dagger:hilt-compiler:latest.release")
|
implementation("com.google.dagger:hilt-compiler:latest.release")
|
||||||
ksp("androidx.hilt:hilt-compiler:latest.release")
|
ksp("androidx.hilt:hilt-compiler:latest.release")
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Start.Splash"
|
android:theme="@style/Theme.Start.Splash"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
android:enableOnBackInvokedCallback="true"
|
android:enableOnBackInvokedCallback="true"
|
||||||
tools:targetApi="tiramisu"
|
tools:targetApi="tiramisu"
|
||||||
tools:ignore="DataExtractionRules">
|
tools:ignore="DataExtractionRules">
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,25 @@ import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.ime
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
||||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||||
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -49,9 +55,10 @@ import ua.acclorite.book_story.presentation.screens.settings.SettingsScreen
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.AppearanceSettings
|
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.AppearanceSettings
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.general.GeneralSettings
|
import ua.acclorite.book_story.presentation.screens.settings.nested.general.GeneralSettings
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.ReaderSettings
|
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.ReaderSettings
|
||||||
import ua.acclorite.book_story.presentation.ui.BooksHistoryResurrectionTheme
|
import ua.acclorite.book_story.presentation.ui.BookStoryTheme
|
||||||
import ua.acclorite.book_story.presentation.ui.Transitions
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
import ua.acclorite.book_story.presentation.ui.isDark
|
import ua.acclorite.book_story.presentation.ui.isDark
|
||||||
|
import ua.acclorite.book_story.presentation.ui.isPureDark
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -109,10 +116,25 @@ class Activity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val imeInsets = WindowInsets.ime
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
|
val isKeyboardVisible by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
imeInsets.getBottom(density) > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(isKeyboardVisible) {
|
||||||
|
if (!isKeyboardVisible) {
|
||||||
|
focusManager.clearFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoaded) {
|
if (isLoaded) {
|
||||||
BooksHistoryResurrectionTheme(
|
BookStoryTheme(
|
||||||
theme = state.theme!!,
|
theme = state.theme!!,
|
||||||
isDark = state.darkTheme!!.isDark(),
|
isDark = state.darkTheme!!.isDark(),
|
||||||
|
isPureDark = state.pureDark!!.isPureDark(this),
|
||||||
themeContrast = state.themeContrast!!
|
themeContrast = state.themeContrast!!
|
||||||
) {
|
) {
|
||||||
NavigationHost(startScreen = Screen.LIBRARY) {
|
NavigationHost(startScreen = Screen.LIBRARY) {
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface DataStore {
|
interface DataStore {
|
||||||
suspend fun <T> getData(key: Preferences.Key<T>, defaultValue: T): Flow<T>
|
suspend fun <T> getData(key: Preferences.Key<T>, defaultValue: T): Flow<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)
|
||||||
}
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import androidx.datastore.preferences.core.emptyPreferences
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -29,6 +30,26 @@ class DataStoreImpl @Inject constructor(context: Application) : DataStore {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun <T> getNullableData(key: Preferences.Key<T>): T? =
|
||||||
|
dataStore.data.catch { exception ->
|
||||||
|
if (exception is IOException) {
|
||||||
|
emit(emptyPreferences())
|
||||||
|
} else {
|
||||||
|
throw exception
|
||||||
|
}
|
||||||
|
}.map { preferences ->
|
||||||
|
val result = preferences[key]
|
||||||
|
result
|
||||||
|
}.firstOrNull()
|
||||||
|
|
||||||
|
override suspend fun getAllData(): Set<Preferences.Key<*>>? {
|
||||||
|
val keys = dataStore.data
|
||||||
|
.map {
|
||||||
|
it.asMap().keys
|
||||||
|
}
|
||||||
|
return keys.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,14 @@ import android.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.local.data_store.DataStore
|
import ua.acclorite.book_story.data.local.data_store.DataStore
|
||||||
|
|
@ -26,7 +31,7 @@ import ua.acclorite.book_story.domain.util.Constants
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import ua.acclorite.book_story.domain.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import ua.acclorite.book_story.presentation.data.calculateFamiliarity
|
import ua.acclorite.book_story.presentation.data.MainState
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
@ -380,6 +385,32 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
dataStore.putData(key, value)
|
dataStore.putData(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getAllSettings(scope: CoroutineScope): MainState {
|
||||||
|
val result = CompletableDeferred<MainState>()
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
val keys = dataStore.getAllData()
|
||||||
|
val data = mutableMapOf<String, Any>()
|
||||||
|
|
||||||
|
val jobs = keys?.map { key ->
|
||||||
|
async {
|
||||||
|
val nullableData = dataStore.getNullableData(key)
|
||||||
|
|
||||||
|
if (nullableData == null) {
|
||||||
|
data.remove(key.name)
|
||||||
|
} else {
|
||||||
|
data[key.name] = nullableData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jobs?.awaitAll()
|
||||||
|
|
||||||
|
result.complete(MainState.initialize(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.await()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getFilesFromDevice(query: String): Flow<Resource<List<File>>> {
|
override suspend fun getFilesFromDevice(query: String): Flow<Resource<List<File>>> {
|
||||||
fun getAllFilesInDirectory(directory: File): List<File> {
|
fun getAllFilesInDirectory(directory: File): List<File> {
|
||||||
val filesList = mutableListOf<File>()
|
val filesList = mutableListOf<File>()
|
||||||
|
|
@ -437,7 +468,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
return@filter false
|
return@filter false
|
||||||
}
|
}
|
||||||
|
|
||||||
val isQuery = if (query.isEmpty()) true else file.name.lowercase()
|
val isQuery = if (query.isEmpty()) true else file.name.trim().lowercase()
|
||||||
.contains(query.trim().lowercase())
|
.contains(query.trim().lowercase())
|
||||||
|
|
||||||
if (!isQuery) {
|
if (!isQuery) {
|
||||||
|
|
@ -451,18 +482,9 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredFiles = if (query.trim().isNotEmpty()) {
|
filteredFiles = filteredFiles.sortedByDescending {
|
||||||
filteredFiles.sortedByDescending {
|
|
||||||
calculateFamiliarity(
|
|
||||||
query,
|
|
||||||
it.name.lowercase().trim()
|
|
||||||
)
|
|
||||||
}.toMutableList()
|
|
||||||
} else {
|
|
||||||
filteredFiles.sortedByDescending {
|
|
||||||
it.lastModified()
|
it.lastModified()
|
||||||
}.toMutableList()
|
}.toMutableList()
|
||||||
}
|
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
Resource.Success(
|
Resource.Success(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package ua.acclorite.book_story.domain.repository
|
package ua.acclorite.book_story.domain.repository
|
||||||
|
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.model.History
|
import ua.acclorite.book_story.domain.model.History
|
||||||
|
|
@ -8,6 +9,7 @@ import ua.acclorite.book_story.domain.model.NullableBook
|
||||||
import ua.acclorite.book_story.domain.model.StringWithId
|
import ua.acclorite.book_story.domain.model.StringWithId
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import ua.acclorite.book_story.domain.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
|
import ua.acclorite.book_story.presentation.data.MainState
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface BookRepository {
|
interface BookRepository {
|
||||||
|
|
@ -55,6 +57,8 @@ interface BookRepository {
|
||||||
value: T
|
value: T
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun getAllSettings(scope: CoroutineScope): MainState
|
||||||
|
|
||||||
suspend fun getFilesFromDevice(query: String = ""): Flow<Resource<List<File>>>
|
suspend fun getFilesFromDevice(query: String = ""): Flow<Resource<List<File>>>
|
||||||
|
|
||||||
suspend fun getBooksFromFiles(files: List<File>): List<NullableBook>
|
suspend fun getBooksFromFiles(files: List<File>): List<NullableBook>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import ua.acclorite.book_story.presentation.data.MainState
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetAllSettings @Inject constructor(
|
||||||
|
private val repository: BookRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun execute(scope: CoroutineScope): MainState {
|
||||||
|
return repository.getAllSettings(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ object DataStoreConstants {
|
||||||
val LANGUAGE = stringPreferencesKey("language")
|
val LANGUAGE = stringPreferencesKey("language")
|
||||||
val THEME = stringPreferencesKey("theme")
|
val THEME = stringPreferencesKey("theme")
|
||||||
val DARK_THEME = stringPreferencesKey("dark_theme")
|
val DARK_THEME = stringPreferencesKey("dark_theme")
|
||||||
|
val PURE_DARK = stringPreferencesKey("pure_dark")
|
||||||
val THEME_CONTRAST = stringPreferencesKey("theme_contrast")
|
val THEME_CONTRAST = stringPreferencesKey("theme_contrast")
|
||||||
val SHOW_START_SCREEN = booleanPreferencesKey("guide")
|
val SHOW_START_SCREEN = booleanPreferencesKey("guide")
|
||||||
val BACKGROUND_COLOR = longPreferencesKey("background_color")
|
val BACKGROUND_COLOR = longPreferencesKey("background_color")
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,3 @@ fun AnimatedTopAppBar(
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package ua.acclorite.book_story.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState(
|
||||||
|
|
||||||
|
): Pair<TopAppBarScrollBehavior, LazyListState> {
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
var canScroll by remember { mutableStateOf(false) }
|
||||||
|
val scrollBehavior = exitUntilCollapsedScrollBehavior(
|
||||||
|
canScroll = {
|
||||||
|
listState.canScrollForward || canScroll
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(scrollBehavior.state) {
|
||||||
|
snapshotFlow {
|
||||||
|
scrollBehavior.state.collapsedFraction
|
||||||
|
}.collectLatest { fraction ->
|
||||||
|
canScroll = fraction > 0.01f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scrollBehavior to listState
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.focusProperties
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -31,6 +32,9 @@ fun CustomIconButton(
|
||||||
CustomTooltip(text = stringResource(id = contentDescription)) {
|
CustomTooltip(text = stringResource(id = contentDescription)) {
|
||||||
IconButton(
|
IconButton(
|
||||||
enabled = enabled && !isClicked,
|
enabled = enabled && !isClicked,
|
||||||
|
modifier = Modifier.focusProperties {
|
||||||
|
canFocus = false
|
||||||
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
if (disableOnClick) {
|
if (disableOnClick) {
|
||||||
isClicked = true
|
isClicked = true
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,15 @@ import androidx.compose.material3.TooltipBox
|
||||||
import androidx.compose.material3.TooltipDefaults
|
import androidx.compose.material3.TooltipDefaults
|
||||||
import androidx.compose.material3.rememberTooltipState
|
import androidx.compose.material3.rememberTooltipState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CustomTooltip(text: String, content: @Composable () -> Unit) {
|
fun CustomTooltip(text: String, padding: Dp = 14.dp, content: @Composable () -> Unit) {
|
||||||
TooltipBox(
|
TooltipBox(
|
||||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(12.dp),
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(padding),
|
||||||
|
focusable = false,
|
||||||
tooltip = {
|
tooltip = {
|
||||||
PlainTooltip {
|
PlainTooltip {
|
||||||
Text(text = text)
|
Text(text = text)
|
||||||
|
|
|
||||||
|
|
@ -34,32 +34,38 @@ fun BottomNavigationBar(
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationBar {
|
NavigationBar {
|
||||||
|
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
item = NavigationItem(
|
item = NavigationItem(
|
||||||
stringResource(id = R.string.library_screen),
|
stringResource(id = R.string.library_screen),
|
||||||
selectedIcon = painterResource(id = R.drawable.library_screen_filled),
|
selectedIcon = painterResource(id = R.drawable.library_screen_filled),
|
||||||
unselectedIcon = painterResource(id = R.drawable.library_screen_outlined)
|
unselectedIcon = painterResource(id = R.drawable.library_screen_outlined)
|
||||||
),
|
),
|
||||||
|
tooltipText = stringResource(id = R.string.library_content_desc),
|
||||||
isSelected = currentScreen == Screen.LIBRARY
|
isSelected = currentScreen == Screen.LIBRARY
|
||||||
) {
|
) {
|
||||||
navigator.navigate(Screen.LIBRARY, false)
|
navigator.navigate(Screen.LIBRARY, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
item = NavigationItem(
|
item = NavigationItem(
|
||||||
stringResource(id = R.string.history_screen),
|
stringResource(id = R.string.history_screen),
|
||||||
selectedIcon = painterResource(id = R.drawable.history_screen_filled),
|
selectedIcon = painterResource(id = R.drawable.history_screen_filled),
|
||||||
unselectedIcon = painterResource(id = R.drawable.history_screen_outlined)
|
unselectedIcon = painterResource(id = R.drawable.history_screen_outlined)
|
||||||
),
|
),
|
||||||
|
tooltipText = stringResource(id = R.string.history_content_desc),
|
||||||
isSelected = currentScreen == Screen.HISTORY
|
isSelected = currentScreen == Screen.HISTORY
|
||||||
) {
|
) {
|
||||||
navigator.navigate(Screen.HISTORY, false)
|
navigator.navigate(Screen.HISTORY, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
item = NavigationItem(
|
item = NavigationItem(
|
||||||
stringResource(id = R.string.browse_screen),
|
stringResource(id = R.string.browse_screen),
|
||||||
selectedIcon = painterResource(id = R.drawable.browse_screen_filled),
|
selectedIcon = painterResource(id = R.drawable.browse_screen_filled),
|
||||||
unselectedIcon = painterResource(id = R.drawable.browse_screen_outlined)
|
unselectedIcon = painterResource(id = R.drawable.browse_screen_outlined)
|
||||||
),
|
),
|
||||||
|
tooltipText = stringResource(id = R.string.browse_content_desc),
|
||||||
isSelected = currentScreen == Screen.BROWSE
|
isSelected = currentScreen == Screen.BROWSE
|
||||||
) {
|
) {
|
||||||
navigator.navigate(Screen.BROWSE, false)
|
navigator.navigate(Screen.BROWSE, false)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.domain.model.NavigationItem
|
import ua.acclorite.book_story.domain.model.NavigationItem
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomTooltip
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bottom Navigation Bar Item, uses default [NavigationBarItem].
|
* Bottom Navigation Bar Item, uses default [NavigationBarItem].
|
||||||
|
|
@ -19,6 +20,7 @@ import ua.acclorite.book_story.domain.model.NavigationItem
|
||||||
fun RowScope.BottomNavigationBarItem(
|
fun RowScope.BottomNavigationBarItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
item: NavigationItem,
|
item: NavigationItem,
|
||||||
|
tooltipText: String,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
@ -27,22 +29,33 @@ fun RowScope.BottomNavigationBarItem(
|
||||||
else item.unselectedIcon
|
else item.unselectedIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
label = {
|
label = {
|
||||||
|
CustomTooltip(
|
||||||
|
text = tooltipText,
|
||||||
|
padding = 64.dp
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = item.title,
|
text = item.title,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
selected = isSelected,
|
selected = isSelected,
|
||||||
onClick = { onClick() },
|
onClick = { onClick() },
|
||||||
icon = {
|
icon = {
|
||||||
|
CustomTooltip(
|
||||||
|
text = tooltipText,
|
||||||
|
padding = 32.dp
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = icon,
|
painter = icon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(24.dp)
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ fun BoxScope.CustomNavigationRail(
|
||||||
selectedIcon = painterResource(id = R.drawable.library_screen_filled),
|
selectedIcon = painterResource(id = R.drawable.library_screen_filled),
|
||||||
unselectedIcon = painterResource(id = R.drawable.library_screen_outlined)
|
unselectedIcon = painterResource(id = R.drawable.library_screen_outlined)
|
||||||
),
|
),
|
||||||
|
tooltipText = stringResource(id = R.string.library_content_desc),
|
||||||
isSelected = currentScreen == Screen.LIBRARY
|
isSelected = currentScreen == Screen.LIBRARY
|
||||||
) {
|
) {
|
||||||
navigator.navigate(Screen.LIBRARY, false)
|
navigator.navigate(Screen.LIBRARY, false)
|
||||||
|
|
@ -86,6 +87,7 @@ fun BoxScope.CustomNavigationRail(
|
||||||
selectedIcon = painterResource(id = R.drawable.history_screen_filled),
|
selectedIcon = painterResource(id = R.drawable.history_screen_filled),
|
||||||
unselectedIcon = painterResource(id = R.drawable.history_screen_outlined)
|
unselectedIcon = painterResource(id = R.drawable.history_screen_outlined)
|
||||||
),
|
),
|
||||||
|
tooltipText = stringResource(id = R.string.history_content_desc),
|
||||||
isSelected = currentScreen == Screen.HISTORY
|
isSelected = currentScreen == Screen.HISTORY
|
||||||
) {
|
) {
|
||||||
navigator.navigate(Screen.HISTORY, false)
|
navigator.navigate(Screen.HISTORY, false)
|
||||||
|
|
@ -96,6 +98,7 @@ fun BoxScope.CustomNavigationRail(
|
||||||
selectedIcon = painterResource(id = R.drawable.browse_screen_filled),
|
selectedIcon = painterResource(id = R.drawable.browse_screen_filled),
|
||||||
unselectedIcon = painterResource(id = R.drawable.browse_screen_outlined)
|
unselectedIcon = painterResource(id = R.drawable.browse_screen_outlined)
|
||||||
),
|
),
|
||||||
|
tooltipText = stringResource(id = R.string.browse_content_desc),
|
||||||
isSelected = currentScreen == Screen.BROWSE
|
isSelected = currentScreen == Screen.BROWSE
|
||||||
) {
|
) {
|
||||||
navigator.navigate(Screen.BROWSE, false)
|
navigator.navigate(Screen.BROWSE, false)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.domain.model.NavigationItem
|
import ua.acclorite.book_story.domain.model.NavigationItem
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomTooltip
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Navigation Rail item.
|
* Custom Navigation Rail item.
|
||||||
|
|
@ -18,6 +19,7 @@ import ua.acclorite.book_story.domain.model.NavigationItem
|
||||||
fun CustomNavigationRailItem(
|
fun CustomNavigationRailItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
item: NavigationItem,
|
item: NavigationItem,
|
||||||
|
tooltipText: String,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
@ -28,20 +30,30 @@ fun CustomNavigationRailItem(
|
||||||
|
|
||||||
NavigationRailItem(
|
NavigationRailItem(
|
||||||
label = {
|
label = {
|
||||||
|
CustomTooltip(
|
||||||
|
text = tooltipText,
|
||||||
|
padding = 48.dp
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = item.title,
|
text = item.title,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
selected = isSelected,
|
selected = isSelected,
|
||||||
onClick = { onClick() },
|
onClick = { onClick() },
|
||||||
icon = {
|
icon = {
|
||||||
|
CustomTooltip(
|
||||||
|
text = tooltipText,
|
||||||
|
padding = 16.dp
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = icon,
|
painter = icon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(24.dp)
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,3 @@ fun String.removeTrailingZero(): String {
|
||||||
|
|
||||||
fun Double.removeDigits(digits: Int) = "%.${digits}f".format(this).replace(",", ".")
|
fun Double.removeDigits(digits: Int) = "%.${digits}f".format(this).replace(",", ".")
|
||||||
|
|
||||||
fun calculateFamiliarity(string: String, target: String): Int {
|
|
||||||
val targetCounts = target.lowercase().trim().groupingBy { it }.eachCount()
|
|
||||||
val familiarity = string.lowercase().trim().sumOf { targetCounts.getOrDefault(it, 0) }
|
|
||||||
return familiarity
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ sealed class MainEvent {
|
||||||
data class OnChangeLanguage(val lang: String) : MainEvent()
|
data class OnChangeLanguage(val lang: String) : MainEvent()
|
||||||
data class OnChangeTheme(val theme: String) : MainEvent()
|
data class OnChangeTheme(val theme: String) : MainEvent()
|
||||||
data class OnChangeDarkTheme(val darkTheme: String) : MainEvent()
|
data class OnChangeDarkTheme(val darkTheme: String) : MainEvent()
|
||||||
|
data class OnChangePureDark(val pureDark: String) : MainEvent()
|
||||||
data class OnChangeThemeContrast(val themeContrast: String) : MainEvent()
|
data class OnChangeThemeContrast(val themeContrast: String) : MainEvent()
|
||||||
data class OnChangeFontFamily(val fontFamily: String) : MainEvent()
|
data class OnChangeFontFamily(val fontFamily: String) : MainEvent()
|
||||||
data class OnChangeFontStyle(val fontStyle: Boolean) : MainEvent()
|
data class OnChangeFontStyle(val fontStyle: Boolean) : MainEvent()
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,21 @@
|
||||||
package ua.acclorite.book_story.presentation.data
|
package ua.acclorite.book_story.presentation.data
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
|
import ua.acclorite.book_story.domain.util.DataStoreConstants
|
||||||
import ua.acclorite.book_story.presentation.ui.DarkTheme
|
import ua.acclorite.book_story.presentation.ui.DarkTheme
|
||||||
|
import ua.acclorite.book_story.presentation.ui.PureDark
|
||||||
import ua.acclorite.book_story.presentation.ui.Theme
|
import ua.acclorite.book_story.presentation.ui.Theme
|
||||||
import ua.acclorite.book_story.presentation.ui.ThemeContrast
|
import ua.acclorite.book_story.presentation.ui.ThemeContrast
|
||||||
|
import ua.acclorite.book_story.presentation.ui.toDarkTheme
|
||||||
|
import ua.acclorite.book_story.presentation.ui.toPureDark
|
||||||
|
import ua.acclorite.book_story.presentation.ui.toTheme
|
||||||
|
import ua.acclorite.book_story.presentation.ui.toThemeContrast
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main State. All app's settings are here. Wrapped in SavedStateHandle, so it won't reset.
|
* Main State. All app's settings are here. Wrapped in SavedStateHandle, so it won't reset.
|
||||||
|
|
@ -15,6 +25,7 @@ data class MainState(
|
||||||
val language: String? = null,
|
val language: String? = null,
|
||||||
val theme: Theme? = null,
|
val theme: Theme? = null,
|
||||||
val darkTheme: DarkTheme? = null,
|
val darkTheme: DarkTheme? = null,
|
||||||
|
val pureDark: PureDark? = null,
|
||||||
val themeContrast: ThemeContrast? = null,
|
val themeContrast: ThemeContrast? = null,
|
||||||
val fontFamily: String? = null,
|
val fontFamily: String? = null,
|
||||||
val isItalic: Boolean? = null,
|
val isItalic: Boolean? = null,
|
||||||
|
|
@ -34,6 +45,8 @@ data class MainState(
|
||||||
// String
|
// String
|
||||||
darkTheme = DarkTheme.valueOf(parcel.readString() ?: DarkTheme.FOLLOW_SYSTEM.name),
|
darkTheme = DarkTheme.valueOf(parcel.readString() ?: DarkTheme.FOLLOW_SYSTEM.name),
|
||||||
// String
|
// String
|
||||||
|
pureDark = PureDark.valueOf(parcel.readString() ?: PureDark.OFF.name),
|
||||||
|
// String
|
||||||
themeContrast = ThemeContrast.valueOf(parcel.readString() ?: ThemeContrast.STANDARD.name),
|
themeContrast = ThemeContrast.valueOf(parcel.readString() ?: ThemeContrast.STANDARD.name),
|
||||||
// String
|
// String
|
||||||
fontFamily = parcel.readString(),
|
fontFamily = parcel.readString(),
|
||||||
|
|
@ -64,6 +77,8 @@ data class MainState(
|
||||||
parcel.writeString(theme?.name)
|
parcel.writeString(theme?.name)
|
||||||
// Dark Theme
|
// Dark Theme
|
||||||
parcel.writeString(darkTheme?.name)
|
parcel.writeString(darkTheme?.name)
|
||||||
|
// Pure Dark
|
||||||
|
parcel.writeString(pureDark?.name)
|
||||||
// Contrast Level
|
// Contrast Level
|
||||||
parcel.writeString(themeContrast?.name)
|
parcel.writeString(themeContrast?.name)
|
||||||
// Font Family
|
// Font Family
|
||||||
|
|
@ -106,5 +121,79 @@ data class MainState(
|
||||||
override fun newArray(size: Int): Array<MainState?> {
|
override fun newArray(size: Int): Array<MainState?> {
|
||||||
return arrayOfNulls(size)
|
return arrayOfNulls(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes [MainState] by given [Map].
|
||||||
|
*/
|
||||||
|
fun initialize(data: Map<String, Any>): MainState {
|
||||||
|
DataStoreConstants.apply {
|
||||||
|
|
||||||
|
val language: String = data[LANGUAGE.name] as? String ?: if (
|
||||||
|
Constants.LANGUAGES.any { Locale.getDefault().language.take(2) == it.first }
|
||||||
|
) {
|
||||||
|
Locale.getDefault().language.take(2)
|
||||||
|
} else {
|
||||||
|
"en"
|
||||||
|
}
|
||||||
|
|
||||||
|
val theme: String = data[THEME.name] as? String ?: if (
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||||
|
) Theme.DYNAMIC.name else Theme.BLUE.name
|
||||||
|
|
||||||
|
val darkTheme: String = data[DARK_THEME.name] as? String
|
||||||
|
?: DarkTheme.FOLLOW_SYSTEM.name
|
||||||
|
|
||||||
|
val pureDark: String = data[PURE_DARK.name] as? String
|
||||||
|
?: PureDark.OFF.name
|
||||||
|
|
||||||
|
val themeContrast: String = data[THEME_CONTRAST.name] as? String
|
||||||
|
?: ThemeContrast.STANDARD.name
|
||||||
|
|
||||||
|
val showStartScreen: Boolean = data[SHOW_START_SCREEN.name] as? Boolean
|
||||||
|
?: true
|
||||||
|
|
||||||
|
val backgroundColor: Long = data[BACKGROUND_COLOR.name] as? Long
|
||||||
|
?: Color.DarkGray.value.toLong()
|
||||||
|
|
||||||
|
val fontColor: Long = data[FONT_COLOR.name] as? Long
|
||||||
|
?: Color.LightGray.value.toLong()
|
||||||
|
|
||||||
|
val fontFamily: String = data[FONT.name] as? String
|
||||||
|
?: Constants.FONTS[0].id
|
||||||
|
|
||||||
|
val isItalic: Boolean = data[IS_ITALIC.name] as? Boolean
|
||||||
|
?: false
|
||||||
|
|
||||||
|
val fontSize: Int = data[FONT_SIZE.name] as? Int
|
||||||
|
?: 16
|
||||||
|
|
||||||
|
val lineHeight: Int = data[LINE_HEIGHT.name] as? Int
|
||||||
|
?: 4
|
||||||
|
|
||||||
|
val paragraphHeight: Int = data[PARAGRAPH_HEIGHT.name] as? Int
|
||||||
|
?: 8
|
||||||
|
|
||||||
|
val paragraphIndentation: Boolean = data[PARAGRAPH_INDENTATION.name] as? Boolean
|
||||||
|
?: false
|
||||||
|
|
||||||
|
|
||||||
|
return MainState(
|
||||||
|
language = language,
|
||||||
|
theme = theme.toTheme(),
|
||||||
|
darkTheme = darkTheme.toDarkTheme(),
|
||||||
|
pureDark = pureDark.toPureDark(),
|
||||||
|
themeContrast = themeContrast.toThemeContrast(),
|
||||||
|
showStartScreen = showStartScreen,
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
fontColor = fontColor,
|
||||||
|
fontFamily = fontFamily,
|
||||||
|
isItalic = isItalic,
|
||||||
|
fontSize = fontSize,
|
||||||
|
lineHeight = lineHeight,
|
||||||
|
paragraphHeight = paragraphHeight,
|
||||||
|
paragraphIndentation = paragraphIndentation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package ua.acclorite.book_story.presentation.data
|
package ua.acclorite.book_story.presentation.data
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
|
@ -9,23 +7,21 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ua.acclorite.book_story.domain.use_case.ChangeLanguage
|
import ua.acclorite.book_story.domain.use_case.ChangeLanguage
|
||||||
import ua.acclorite.book_story.domain.use_case.GetDatastore
|
import ua.acclorite.book_story.domain.use_case.GetAllSettings
|
||||||
import ua.acclorite.book_story.domain.use_case.SetDatastore
|
import ua.acclorite.book_story.domain.use_case.SetDatastore
|
||||||
import ua.acclorite.book_story.domain.util.Constants
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
import ua.acclorite.book_story.domain.util.DataStoreConstants
|
import ua.acclorite.book_story.domain.util.DataStoreConstants
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
||||||
import ua.acclorite.book_story.presentation.ui.DarkTheme
|
|
||||||
import ua.acclorite.book_story.presentation.ui.Theme
|
|
||||||
import ua.acclorite.book_story.presentation.ui.ThemeContrast
|
|
||||||
import ua.acclorite.book_story.presentation.ui.toDarkTheme
|
import ua.acclorite.book_story.presentation.ui.toDarkTheme
|
||||||
|
import ua.acclorite.book_story.presentation.ui.toPureDark
|
||||||
import ua.acclorite.book_story.presentation.ui.toTheme
|
import ua.acclorite.book_story.presentation.ui.toTheme
|
||||||
import ua.acclorite.book_story.presentation.ui.toThemeContrast
|
import ua.acclorite.book_story.presentation.ui.toThemeContrast
|
||||||
import java.util.Locale
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -35,14 +31,17 @@ import javax.inject.Inject
|
||||||
class MainViewModel @Inject constructor(
|
class MainViewModel @Inject constructor(
|
||||||
private val stateHandle: SavedStateHandle,
|
private val stateHandle: SavedStateHandle,
|
||||||
|
|
||||||
private val getDatastore: GetDatastore,
|
|
||||||
private val setDatastore: SetDatastore,
|
private val setDatastore: SetDatastore,
|
||||||
private val changeLanguage: ChangeLanguage,
|
private val changeLanguage: ChangeLanguage,
|
||||||
|
private val getAllSettings: GetAllSettings
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _isReady = MutableStateFlow(false)
|
private val _isReady = MutableStateFlow(false)
|
||||||
val isReady = _isReady.asStateFlow()
|
val isReady = _isReady.asStateFlow()
|
||||||
|
|
||||||
|
private val isSettingsReady = MutableStateFlow(false)
|
||||||
|
private val isViewModelReady = MutableStateFlow(false)
|
||||||
|
|
||||||
private val _state: MutableStateFlow<MainState> = MutableStateFlow(
|
private val _state: MutableStateFlow<MainState> = MutableStateFlow(
|
||||||
stateHandle[Constants.MAIN_STATE] ?: MainState()
|
stateHandle[Constants.MAIN_STATE] ?: MainState()
|
||||||
)
|
)
|
||||||
|
|
@ -53,7 +52,7 @@ class MainViewModel @Inject constructor(
|
||||||
is MainEvent.OnChangeLanguage -> {
|
is MainEvent.OnChangeLanguage -> {
|
||||||
viewModelScope.launch(Dispatchers.Main) {
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
changeLanguage.execute(event.lang)
|
changeLanguage.execute(event.lang)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
language = event.lang
|
language = event.lang
|
||||||
)
|
)
|
||||||
|
|
@ -64,7 +63,7 @@ class MainViewModel @Inject constructor(
|
||||||
is MainEvent.OnChangeDarkTheme -> {
|
is MainEvent.OnChangeDarkTheme -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.DARK_THEME, event.darkTheme)
|
setDatastore.execute(DataStoreConstants.DARK_THEME, event.darkTheme)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
darkTheme = event.darkTheme.toDarkTheme()
|
darkTheme = event.darkTheme.toDarkTheme()
|
||||||
)
|
)
|
||||||
|
|
@ -72,10 +71,21 @@ class MainViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangePureDark -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(DataStoreConstants.PURE_DARK, event.pureDark)
|
||||||
|
updateStateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
pureDark = event.pureDark.toPureDark()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
is MainEvent.OnChangeThemeContrast -> {
|
is MainEvent.OnChangeThemeContrast -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.THEME_CONTRAST, event.themeContrast)
|
setDatastore.execute(DataStoreConstants.THEME_CONTRAST, event.themeContrast)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
themeContrast = event.themeContrast.toThemeContrast()
|
themeContrast = event.themeContrast.toThemeContrast()
|
||||||
)
|
)
|
||||||
|
|
@ -86,7 +96,7 @@ class MainViewModel @Inject constructor(
|
||||||
is MainEvent.OnChangeTheme -> {
|
is MainEvent.OnChangeTheme -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.THEME, event.theme)
|
setDatastore.execute(DataStoreConstants.THEME, event.theme)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
theme = event.theme.toTheme()
|
theme = event.theme.toTheme()
|
||||||
)
|
)
|
||||||
|
|
@ -97,7 +107,7 @@ class MainViewModel @Inject constructor(
|
||||||
is MainEvent.OnChangeFontFamily -> {
|
is MainEvent.OnChangeFontFamily -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.FONT, event.fontFamily)
|
setDatastore.execute(DataStoreConstants.FONT, event.fontFamily)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
fontFamily = Constants.FONTS.find { font -> font.id == event.fontFamily }?.id
|
fontFamily = Constants.FONTS.find { font -> font.id == event.fontFamily }?.id
|
||||||
?: Constants.FONTS[0].id
|
?: Constants.FONTS[0].id
|
||||||
|
|
@ -109,7 +119,7 @@ class MainViewModel @Inject constructor(
|
||||||
is MainEvent.OnChangeFontStyle -> {
|
is MainEvent.OnChangeFontStyle -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.IS_ITALIC, event.fontStyle)
|
setDatastore.execute(DataStoreConstants.IS_ITALIC, event.fontStyle)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
isItalic = event.fontStyle
|
isItalic = event.fontStyle
|
||||||
)
|
)
|
||||||
|
|
@ -120,7 +130,7 @@ class MainViewModel @Inject constructor(
|
||||||
is MainEvent.OnChangeFontSize -> {
|
is MainEvent.OnChangeFontSize -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.FONT_SIZE, event.fontSize)
|
setDatastore.execute(DataStoreConstants.FONT_SIZE, event.fontSize)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
fontSize = event.fontSize
|
fontSize = event.fontSize
|
||||||
)
|
)
|
||||||
|
|
@ -131,7 +141,7 @@ class MainViewModel @Inject constructor(
|
||||||
is MainEvent.OnChangeLineHeight -> {
|
is MainEvent.OnChangeLineHeight -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.LINE_HEIGHT, event.lineHeight)
|
setDatastore.execute(DataStoreConstants.LINE_HEIGHT, event.lineHeight)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
lineHeight = event.lineHeight
|
lineHeight = event.lineHeight
|
||||||
)
|
)
|
||||||
|
|
@ -142,7 +152,7 @@ class MainViewModel @Inject constructor(
|
||||||
is MainEvent.OnChangeParagraphHeight -> {
|
is MainEvent.OnChangeParagraphHeight -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.PARAGRAPH_HEIGHT, event.paragraphHeight)
|
setDatastore.execute(DataStoreConstants.PARAGRAPH_HEIGHT, event.paragraphHeight)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
paragraphHeight = event.paragraphHeight
|
paragraphHeight = event.paragraphHeight
|
||||||
)
|
)
|
||||||
|
|
@ -153,7 +163,7 @@ class MainViewModel @Inject constructor(
|
||||||
is MainEvent.OnChangeParagraphIndentation -> {
|
is MainEvent.OnChangeParagraphIndentation -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.PARAGRAPH_INDENTATION, event.bool)
|
setDatastore.execute(DataStoreConstants.PARAGRAPH_INDENTATION, event.bool)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
paragraphIndentation = event.bool
|
paragraphIndentation = event.bool
|
||||||
)
|
)
|
||||||
|
|
@ -164,7 +174,7 @@ class MainViewModel @Inject constructor(
|
||||||
is MainEvent.OnChangeBackgroundColor -> {
|
is MainEvent.OnChangeBackgroundColor -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.BACKGROUND_COLOR, event.color)
|
setDatastore.execute(DataStoreConstants.BACKGROUND_COLOR, event.color)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
backgroundColor = event.color
|
backgroundColor = event.color
|
||||||
)
|
)
|
||||||
|
|
@ -175,7 +185,7 @@ class MainViewModel @Inject constructor(
|
||||||
is MainEvent.OnChangeFontColor -> {
|
is MainEvent.OnChangeFontColor -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.FONT_COLOR, event.color)
|
setDatastore.execute(DataStoreConstants.FONT_COLOR, event.color)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
fontColor = event.color
|
fontColor = event.color
|
||||||
)
|
)
|
||||||
|
|
@ -186,7 +196,7 @@ class MainViewModel @Inject constructor(
|
||||||
is MainEvent.OnChangeShowStartScreen -> {
|
is MainEvent.OnChangeShowStartScreen -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.SHOW_START_SCREEN, event.bool)
|
setDatastore.execute(DataStoreConstants.SHOW_START_SCREEN, event.bool)
|
||||||
_state.updateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
it.copy(
|
it.copy(
|
||||||
showStartScreen = event.bool
|
showStartScreen = event.bool
|
||||||
)
|
)
|
||||||
|
|
@ -199,204 +209,54 @@ class MainViewModel @Inject constructor(
|
||||||
fun init(
|
fun init(
|
||||||
libraryViewModel: LibraryViewModel,
|
libraryViewModel: LibraryViewModel,
|
||||||
) {
|
) {
|
||||||
val isViewModelsReady = combine(
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
libraryViewModel.isReady,
|
val settings = getAllSettings.execute(viewModelScope)
|
||||||
) { values ->
|
|
||||||
values.all { it }
|
// All additional execution
|
||||||
|
changeLanguage.execute(settings.language!!)
|
||||||
|
|
||||||
|
updateStateWithSavedHandle {
|
||||||
|
settings
|
||||||
|
}
|
||||||
|
isSettingsReady.update { true }
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDataReady = combine(
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
_state
|
libraryViewModel.isReady.collectLatest { ready ->
|
||||||
) {
|
isViewModelReady.update {
|
||||||
val value = it.first()
|
ready
|
||||||
|
}
|
||||||
return@combine value.language != null &&
|
}
|
||||||
value.theme != null &&
|
|
||||||
value.darkTheme != null &&
|
|
||||||
value.themeContrast != null &&
|
|
||||||
value.fontFamily != null &&
|
|
||||||
value.isItalic != null &&
|
|
||||||
value.fontSize != null &&
|
|
||||||
value.lineHeight != null &&
|
|
||||||
value.paragraphHeight != null &&
|
|
||||||
value.paragraphIndentation != null &&
|
|
||||||
value.backgroundColor != null &&
|
|
||||||
value.fontColor != null &&
|
|
||||||
value.showStartScreen != null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isReady = combine(
|
val isReady = combine(
|
||||||
isViewModelsReady,
|
isViewModelReady,
|
||||||
isDataReady
|
isSettingsReady
|
||||||
) { values ->
|
) { values ->
|
||||||
values.all { it }
|
values.all { it }
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
isReady.collect { bool ->
|
isReady.first { bool ->
|
||||||
if (bool) {
|
if (bool) {
|
||||||
_isReady.update {
|
_isReady.update {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
return@collect
|
}
|
||||||
|
bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Language
|
|
||||||
viewModelScope.launch(Dispatchers.Main) {
|
|
||||||
getDatastore
|
|
||||||
.execute(
|
|
||||||
DataStoreConstants.LANGUAGE,
|
|
||||||
if (Constants.LANGUAGES.any { Locale.getDefault().language.take(2) == it.first }) {
|
|
||||||
Locale.getDefault().language.take(2)
|
|
||||||
} else {
|
|
||||||
"en"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeLanguage(it))
|
|
||||||
it.isNotBlank()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theme
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
getDatastore
|
|
||||||
.execute(
|
|
||||||
DataStoreConstants.THEME,
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Theme.DYNAMIC.name
|
|
||||||
else Theme.BLUE.name
|
|
||||||
)
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeTheme(it))
|
|
||||||
it.isNotBlank()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dark Theme
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
getDatastore
|
|
||||||
.execute(DataStoreConstants.DARK_THEME, DarkTheme.FOLLOW_SYSTEM.name)
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeDarkTheme(it))
|
|
||||||
it.isNotBlank()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theme Contrast
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
getDatastore
|
|
||||||
.execute(DataStoreConstants.THEME_CONTRAST, ThemeContrast.STANDARD.name)
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeThemeContrast(it))
|
|
||||||
it.isNotBlank()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show Start Screen
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
getDatastore
|
|
||||||
.execute(DataStoreConstants.SHOW_START_SCREEN, true)
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeShowStartScreen(it))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Background Color
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
getDatastore
|
|
||||||
.execute(DataStoreConstants.BACKGROUND_COLOR, Color.DarkGray.value.toLong())
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeBackgroundColor(it))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Font Color
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
getDatastore
|
|
||||||
.execute(DataStoreConstants.FONT_COLOR, Color.LightGray.value.toLong())
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeFontColor(it))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Font Family
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
getDatastore
|
|
||||||
.execute(DataStoreConstants.FONT, Constants.FONTS[0].id)
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeFontFamily(it))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Font Style
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
getDatastore
|
|
||||||
.execute(DataStoreConstants.IS_ITALIC, false)
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeFontStyle(it))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Font Size
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
getDatastore
|
|
||||||
.execute(DataStoreConstants.FONT_SIZE, 16)
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeFontSize(it))
|
|
||||||
it > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Line Height
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
getDatastore
|
|
||||||
.execute(DataStoreConstants.LINE_HEIGHT, 4)
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeLineHeight(it))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paragraph Height
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
getDatastore
|
|
||||||
.execute(DataStoreConstants.PARAGRAPH_HEIGHT, 8)
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeParagraphHeight(it))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paragraph Indentation
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
getDatastore
|
|
||||||
.execute(DataStoreConstants.PARAGRAPH_INDENTATION, false)
|
|
||||||
.first {
|
|
||||||
onEvent(MainEvent.OnChangeParagraphIndentation(it))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates [MutableStateFlow] along with [SavedStateHandle].
|
* Updates [MainState] along with [SavedStateHandle].
|
||||||
*/
|
*/
|
||||||
private fun <T> MutableStateFlow<T>.updateWithSavedHandle(
|
private fun updateStateWithSavedHandle(
|
||||||
const: String = Constants.MAIN_STATE,
|
function: (MainState) -> MainState
|
||||||
function: (T) -> T
|
|
||||||
) {
|
) {
|
||||||
val nextValue = function(value)
|
_state.update {
|
||||||
update {
|
stateHandle[Constants.MAIN_STATE] = function(it)
|
||||||
stateHandle[const] = nextValue
|
function(it)
|
||||||
nextValue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
|
@ -32,6 +31,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
|
import ua.acclorite.book_story.presentation.components.collapsibleScrollBehaviorWithLazyListState
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.screens.about.components.AboutItem
|
import ua.acclorite.book_story.presentation.screens.about.components.AboutItem
|
||||||
import ua.acclorite.book_story.presentation.screens.about.data.AboutEvent
|
import ua.acclorite.book_story.presentation.screens.about.data.AboutEvent
|
||||||
|
|
@ -44,17 +44,12 @@ fun AboutScreen(
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val listState = rememberLazyListState()
|
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
|
||||||
canScroll = {
|
|
||||||
listState.canScrollForward || listState.canScrollBackward
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|
@ -65,7 +60,7 @@ fun AboutScreen(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
GoBackButton(navigator = navigator)
|
GoBackButton(navigator = navigator)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollState.first,
|
||||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||||
|
|
@ -77,7 +72,7 @@ fun AboutScreen(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = paddingValues.calculateTopPadding()),
|
.padding(top = paddingValues.calculateTopPadding()),
|
||||||
state = listState
|
state = scrollState.second
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ fun BookInfoScreen(
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val snackbarState = remember { SnackbarHostState() }
|
val snackbarState = remember { SnackbarHostState() }
|
||||||
val refreshState = rememberPullRefreshState(
|
val refreshState = rememberPullRefreshState(
|
||||||
refreshing = state.isRefreshing,
|
refreshing = state.isRefreshing || state.isLoadingUpdate,
|
||||||
onRefresh = {
|
onRefresh = {
|
||||||
viewModel.onEvent(
|
viewModel.onEvent(
|
||||||
BookInfoEvent.OnLoadUpdate(
|
BookInfoEvent.OnLoadUpdate(
|
||||||
|
|
@ -161,7 +161,9 @@ fun BookInfoScreen(
|
||||||
isTopBarScrolled = null,
|
isTopBarScrolled = null,
|
||||||
|
|
||||||
content1NavigationIcon = {
|
content1NavigationIcon = {
|
||||||
GoBackButton(navigator = navigator, enabled = !state.isRefreshing)
|
GoBackButton(navigator = navigator, enabled = !state.isRefreshing) {
|
||||||
|
viewModel.onEvent(BookInfoEvent.OnCancelUpdate)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
content1Title = {
|
content1Title = {
|
||||||
DefaultTransition(visible = firstVisibleItemIndex > 0) {
|
DefaultTransition(visible = firstVisibleItemIndex > 0) {
|
||||||
|
|
@ -179,7 +181,7 @@ fun BookInfoScreen(
|
||||||
icon = Icons.Default.Refresh,
|
icon = Icons.Default.Refresh,
|
||||||
contentDescription = R.string.refresh_book_content_desc,
|
contentDescription = R.string.refresh_book_content_desc,
|
||||||
disableOnClick = false,
|
disableOnClick = false,
|
||||||
enabled = !state.isRefreshing
|
enabled = !state.isRefreshing && !state.isLoadingUpdate
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(
|
viewModel.onEvent(
|
||||||
BookInfoEvent.OnLoadUpdate(
|
BookInfoEvent.OnLoadUpdate(
|
||||||
|
|
@ -319,7 +321,7 @@ fun BookInfoScreen(
|
||||||
)
|
)
|
||||||
|
|
||||||
PullRefreshIndicator(
|
PullRefreshIndicator(
|
||||||
state.isRefreshing,
|
state.isRefreshing || state.isLoadingUpdate,
|
||||||
refreshState,
|
refreshState,
|
||||||
Modifier
|
Modifier
|
||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
|
|
@ -332,6 +334,7 @@ fun BookInfoScreen(
|
||||||
|
|
||||||
BackHandler {
|
BackHandler {
|
||||||
if (!state.isRefreshing) {
|
if (!state.isRefreshing) {
|
||||||
|
viewModel.onEvent(BookInfoEvent.OnCancelUpdate)
|
||||||
navigator.navigateBack()
|
navigator.navigateBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
|
||||||
fontFamily = MaterialTheme.typography.headlineSmall.fontFamily
|
fontFamily = MaterialTheme.typography.headlineSmall.fontFamily
|
||||||
),
|
),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
if (it.length < 80) {
|
if (it.length < 100 || it.length < state.titleValue.length) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnTitleValueChange(it))
|
viewModel.onEvent(BookInfoEvent.OnTitleValueChange(it))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@ sealed class BookInfoEvent {
|
||||||
val textUpdated: Boolean,
|
val textUpdated: Boolean,
|
||||||
) : BookInfoEvent()
|
) : BookInfoEvent()
|
||||||
|
|
||||||
|
data object OnCancelUpdate : BookInfoEvent()
|
||||||
|
|
||||||
data class OnConfirmUpdate(
|
data class OnConfirmUpdate(
|
||||||
val snackbarState: SnackbarHostState,
|
val snackbarState: SnackbarHostState,
|
||||||
val context: Context,
|
val context: Context,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import ua.acclorite.book_story.domain.util.Constants
|
||||||
data class BookInfoState(
|
data class BookInfoState(
|
||||||
val book: Book = Constants.EMPTY_BOOK,
|
val book: Book = Constants.EMPTY_BOOK,
|
||||||
|
|
||||||
|
val isLoadingUpdate: Boolean = false,
|
||||||
val isRefreshing: Boolean = false,
|
val isRefreshing: Boolean = false,
|
||||||
val showConfirmUpdateDialog: Boolean = false,
|
val showConfirmUpdateDialog: Boolean = false,
|
||||||
val updatedBook: Book? = null,
|
val updatedBook: Book? = null,
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
val state = _state.asStateFlow()
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
|
private var job2: Job? = null
|
||||||
|
|
||||||
fun onEvent(event: BookInfoEvent) {
|
fun onEvent(event: BookInfoEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
|
|
@ -290,14 +291,17 @@ class BookInfoViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnLoadUpdate -> {
|
is BookInfoEvent.OnLoadUpdate -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
onEvent(BookInfoEvent.OnCancelUpdate)
|
||||||
|
|
||||||
|
job2 = viewModelScope.launch(Dispatchers.IO) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = true,
|
isLoadingUpdate = true,
|
||||||
editTitle = false
|
editTitle = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yield()
|
||||||
if (_state.value.book.file == null) {
|
if (_state.value.book.file == null) {
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnShowSnackbar(
|
BookInfoEvent.OnShowSnackbar(
|
||||||
|
|
@ -323,13 +327,15 @@ class BookInfoViewModel @Inject constructor(
|
||||||
delay(500)
|
delay(500)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = false
|
isLoadingUpdate = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yield()
|
||||||
val nullableBook = getBookFromFile.execute(_state.value.book.file!!)
|
val nullableBook = getBookFromFile.execute(_state.value.book.file!!)
|
||||||
|
yield()
|
||||||
if (nullableBook is NullableBook.Null) {
|
if (nullableBook is NullableBook.Null) {
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnShowSnackbar(
|
BookInfoEvent.OnShowSnackbar(
|
||||||
|
|
@ -352,11 +358,12 @@ class BookInfoViewModel @Inject constructor(
|
||||||
delay(500)
|
delay(500)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = false
|
isLoadingUpdate = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
yield()
|
||||||
|
|
||||||
val updatedBook = nullableBook.book?.first ?: return@launch
|
val updatedBook = nullableBook.book?.first ?: return@launch
|
||||||
val book = _state.value.book
|
val book = _state.value.book
|
||||||
|
|
@ -383,6 +390,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
textUpdated = true
|
textUpdated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yield()
|
||||||
if (!authorUpdated && !descriptionUpdated && !textUpdated) {
|
if (!authorUpdated && !descriptionUpdated && !textUpdated) {
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnShowSnackbar(
|
BookInfoEvent.OnShowSnackbar(
|
||||||
|
|
@ -395,12 +403,13 @@ class BookInfoViewModel @Inject constructor(
|
||||||
delay(500)
|
delay(500)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = false
|
isLoadingUpdate = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yield()
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnShowConfirmUpdateDialog(
|
BookInfoEvent.OnShowConfirmUpdateDialog(
|
||||||
updatedBook = updatedBook,
|
updatedBook = updatedBook,
|
||||||
|
|
@ -412,7 +421,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = false
|
isLoadingUpdate = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -585,8 +594,19 @@ class BookInfoViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is BookInfoEvent.OnCancelUpdate -> {
|
||||||
|
_state.update {
|
||||||
|
job2?.cancel()
|
||||||
|
it.copy(
|
||||||
|
showConfirmUpdateDialog = false,
|
||||||
|
isLoadingUpdate = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnNavigateToReaderScreen -> {
|
is BookInfoEvent.OnNavigateToReaderScreen -> {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
onEvent(BookInfoEvent.OnCancelUpdate)
|
||||||
_state.value.book.id.let {
|
_state.value.book.id.let {
|
||||||
insertHistory.execute(
|
insertHistory.execute(
|
||||||
listOf(
|
listOf(
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
|
@ -71,7 +70,6 @@ import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalMaterial3Api::class,
|
ExperimentalMaterial3Api::class,
|
||||||
ExperimentalPermissionsApi::class,
|
ExperimentalPermissionsApi::class,
|
||||||
ExperimentalFoundationApi::class,
|
|
||||||
ExperimentalMaterialApi::class
|
ExperimentalMaterialApi::class
|
||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -249,7 +247,7 @@ fun BrowseScreen(
|
||||||
) { selectableFile ->
|
) { selectableFile ->
|
||||||
BrowseFileItem(
|
BrowseFileItem(
|
||||||
file = selectableFile,
|
file = selectableFile,
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
hasSelectedFiles = state.selectableFiles.any { it.second },
|
hasSelectedFiles = state.selectableFiles.any { it.second },
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.LargeTopAppBar
|
import androidx.compose.material3.LargeTopAppBar
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -18,34 +18,38 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||||
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 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.hilt.navigation.compose.hiltViewModel
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
|
import ua.acclorite.book_story.presentation.components.collapsibleScrollBehaviorWithLazyListState
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpClickableNote
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpAddBooksItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpClickMeNoteItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpCustomizeApp
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpCustomizeReader
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpEditBook
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpFindBooksItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpMoveOrDeleteBooks
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpReadBook
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpUpdateBook
|
||||||
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(
|
||||||
|
ExperimentalMaterial3Api::class
|
||||||
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun HelpScreen(
|
fun HelpScreen(
|
||||||
viewModel: HelpViewModel = hiltViewModel(),
|
viewModel: HelpViewModel = hiltViewModel(),
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
||||||
val listState = rememberLazyListState()
|
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
|
||||||
canScroll = {
|
|
||||||
listState.canScrollForward || listState.canScrollBackward
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|
@ -56,7 +60,7 @@ fun HelpScreen(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
GoBackButton(navigator = navigator)
|
GoBackButton(navigator = navigator)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollState.first,
|
||||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||||
|
|
@ -67,19 +71,88 @@ fun HelpScreen(
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = paddingValues.calculateTopPadding())
|
.padding(top = paddingValues.calculateTopPadding(), start = 18.dp, end = 18.dp)
|
||||||
.padding(horizontal = 18.dp),
|
.imePadding(),
|
||||||
state = listState
|
state = scrollState.second
|
||||||
) {
|
) {
|
||||||
item { Spacer(modifier = Modifier.height(16.dp)) }
|
item {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.animateItem()
|
||||||
|
.height(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
item { HelpClickableNote() }
|
item {
|
||||||
|
HelpClickMeNoteItem(viewModel = viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
item { Spacer(modifier = Modifier.height(16.dp)) }
|
item {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.animateItem()
|
||||||
|
.height(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO Help screen */
|
|
||||||
|
|
||||||
item { Spacer(modifier = Modifier.height(48.dp)) }
|
item {
|
||||||
|
HelpFindBooksItem(viewModel = viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HelpAddBooksItem(navigator = navigator, viewModel = viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HelpCustomizeApp(
|
||||||
|
navigator = navigator,
|
||||||
|
viewModel = viewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HelpMoveOrDeleteBooks(
|
||||||
|
navigator = navigator,
|
||||||
|
viewModel = viewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HelpEditBook(
|
||||||
|
navigator = navigator,
|
||||||
|
viewModel = viewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HelpReadBook(
|
||||||
|
navigator = navigator,
|
||||||
|
viewModel = viewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HelpCustomizeReader(
|
||||||
|
navigator = navigator,
|
||||||
|
viewModel = viewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HelpUpdateBook(
|
||||||
|
navigator = navigator,
|
||||||
|
viewModel = viewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.animateItem()
|
||||||
|
.height(48.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.components
|
||||||
|
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AnnotatedString.Builder.HelpAnnotation(
|
||||||
|
tag: String,
|
||||||
|
block: @Composable AnnotatedString.Builder.() -> Unit
|
||||||
|
) {
|
||||||
|
pushStringAnnotation(tag = tag, annotation = "")
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
pop()
|
||||||
|
}
|
||||||
|
|
@ -1,79 +1,69 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.help.components
|
package ua.acclorite.book_story.presentation.screens.help.components
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.foundation.text.ClickableText
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Info
|
import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
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.components.CustomTooltip
|
||||||
|
import ua.acclorite.book_story.presentation.ui.SlidingTransition
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
/**
|
||||||
|
* When clicked returns a Tag from [tags] (if present in [text])
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun HelpClickableNote() {
|
fun LazyItemScope.HelpClickableNote(
|
||||||
val context = LocalContext.current
|
text: AnnotatedString,
|
||||||
|
tags: List<String> = emptyList(),
|
||||||
Column {
|
isNoteFullyShown: Boolean,
|
||||||
|
onIconClick: () -> Unit,
|
||||||
|
onTagClick: (String) -> Unit
|
||||||
|
) {
|
||||||
|
Column(Modifier.animateItem()) {
|
||||||
|
CustomTooltip(text = stringResource(R.string.note_content_desc)) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Info,
|
imageVector = Icons.Outlined.Info,
|
||||||
contentDescription = stringResource(id = R.string.note_content_desc),
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(20.dp),
|
.size(20.dp)
|
||||||
|
.clickable(interactionSource = null, indication = null) {
|
||||||
|
onIconClick()
|
||||||
|
},
|
||||||
|
contentDescription = stringResource(R.string.note_content_desc),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
FlowRow(verticalArrangement = Arrangement.spacedBy(1.dp)) {
|
|
||||||
Text(
|
|
||||||
stringResource(id = R.string.clickable_note_1) + " ",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
modifier = Modifier.align(Alignment.CenterVertically)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
stringResource(id = R.string.clickable_note_2) + " ",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
fontWeight = FontWeight.Medium,
|
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterVertically)
|
|
||||||
.clickable(
|
|
||||||
interactionSource = null,
|
|
||||||
indication = null,
|
|
||||||
onClick = {
|
|
||||||
Toast
|
|
||||||
.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.clickable_note_action),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
stringResource(id = R.string.clickable_note_3),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
modifier = Modifier.align(Alignment.CenterVertically)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SlidingTransition(visible = isNoteFullyShown) {
|
||||||
|
Column {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
ClickableText(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodySmall.copy(
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
) { offset ->
|
||||||
|
tags.forEach { tag ->
|
||||||
|
text.getStringAnnotations(tag = tag, start = offset, end = offset)
|
||||||
|
.firstOrNull()?.let {
|
||||||
|
onTagClick(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.foundation.text.ClickableText
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.ArrowDropUp
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.ui.SlidingTransition
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.HelpItem(
|
||||||
|
title: String,
|
||||||
|
description: AnnotatedString,
|
||||||
|
customContent: @Composable ColumnScope.() -> Unit = {},
|
||||||
|
tags: List<String>,
|
||||||
|
shouldShowDescription: Boolean,
|
||||||
|
onTitleClick: () -> Unit,
|
||||||
|
onTagClick: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val animatedArrowRotation by animateFloatAsState(
|
||||||
|
targetValue = if (shouldShowDescription) 0f else -180f,
|
||||||
|
animationSpec = tween(300),
|
||||||
|
label = stringResource(id = R.string.arrow_anim_content_desc)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Column(Modifier.animateItem()) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.height(8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(interactionSource = null, indication = null) {
|
||||||
|
onTitleClick()
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.ArrowDropUp,
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.rotate(animatedArrowRotation),
|
||||||
|
contentDescription = stringResource(id = R.string.arrow_content_desc)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SlidingTransition(
|
||||||
|
visible = shouldShowDescription
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
ClickableText(
|
||||||
|
text = description,
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
) { offset ->
|
||||||
|
tags.forEach { tag ->
|
||||||
|
description.getStringAnnotations(tag = tag, start = offset, end = offset)
|
||||||
|
.firstOrNull()?.let {
|
||||||
|
onTagClick(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.height(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.HelpAddBooksItem(navigator: Navigator, viewModel: HelpViewModel) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
HelpItem(
|
||||||
|
title = stringResource(id = R.string.help_title_how_to_add_books),
|
||||||
|
description = buildAnnotatedString {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_add_books_1) + " ")
|
||||||
|
|
||||||
|
HelpAnnotation(tag = "browse") {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_add_books_2))
|
||||||
|
}
|
||||||
|
append(". ")
|
||||||
|
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_add_books_3) + " ")
|
||||||
|
|
||||||
|
HelpAnnotation(tag = "library") {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_add_books_4))
|
||||||
|
}
|
||||||
|
append(".")
|
||||||
|
},
|
||||||
|
tags = listOf("browse", "library"),
|
||||||
|
shouldShowDescription = state.showHelpItem2,
|
||||||
|
onTitleClick = {
|
||||||
|
viewModel.onUpdate {
|
||||||
|
it.copy(
|
||||||
|
showHelpItem2 = !it.showHelpItem2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTagClick = { tag ->
|
||||||
|
when (tag) {
|
||||||
|
"browse" -> {
|
||||||
|
navigator.navigate(Screen.BROWSE, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
"library" -> {
|
||||||
|
navigator.navigate(Screen.LIBRARY, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpClickableNote
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.HelpClickMeNoteItem(viewModel: HelpViewModel) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
HelpClickableNote(
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
append(stringResource(id = R.string.help_clickable_note_1) + " ")
|
||||||
|
|
||||||
|
HelpAnnotation(tag = "note") {
|
||||||
|
append(stringResource(id = R.string.help_clickable_note_2))
|
||||||
|
}
|
||||||
|
|
||||||
|
append(" " + stringResource(id = R.string.help_clickable_note_3))
|
||||||
|
},
|
||||||
|
tags = listOf("note"),
|
||||||
|
isNoteFullyShown = state.showNote1,
|
||||||
|
onIconClick = {
|
||||||
|
viewModel.onUpdate {
|
||||||
|
it.copy(
|
||||||
|
showNote1 = !it.showNote1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTagClick = { tag ->
|
||||||
|
when (tag) {
|
||||||
|
"note" -> {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.help_clickable_note_action),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.HelpCustomizeApp(viewModel: HelpViewModel, navigator: Navigator) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
HelpItem(
|
||||||
|
title = stringResource(id = R.string.help_title_how_to_customize_app),
|
||||||
|
description = buildAnnotatedString {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_customize_app_1) + " ")
|
||||||
|
|
||||||
|
HelpAnnotation(tag = "settings") {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_customize_app_2))
|
||||||
|
}
|
||||||
|
append(". ")
|
||||||
|
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_customize_app_3))
|
||||||
|
},
|
||||||
|
tags = listOf("settings"),
|
||||||
|
shouldShowDescription = state.showHelpItem3,
|
||||||
|
onTitleClick = {
|
||||||
|
viewModel.onUpdate {
|
||||||
|
it.copy(
|
||||||
|
showHelpItem3 = !it.showHelpItem3
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTagClick = { tag ->
|
||||||
|
when (tag) {
|
||||||
|
"settings" -> {
|
||||||
|
navigator.navigate(Screen.SETTINGS, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.HelpCustomizeReader(viewModel: HelpViewModel, navigator: Navigator) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
HelpItem(
|
||||||
|
title = stringResource(id = R.string.help_title_how_to_customize_reader),
|
||||||
|
description = buildAnnotatedString {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_customize_reader_1) + " ")
|
||||||
|
|
||||||
|
HelpAnnotation(tag = "settings") {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_customize_reader_2))
|
||||||
|
}
|
||||||
|
append(" ")
|
||||||
|
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_customize_reader_3))
|
||||||
|
},
|
||||||
|
tags = listOf("settings"),
|
||||||
|
shouldShowDescription = state.showHelpItem7,
|
||||||
|
onTitleClick = {
|
||||||
|
viewModel.onUpdate {
|
||||||
|
it.copy(
|
||||||
|
showHelpItem7 = !it.showHelpItem7
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTagClick = { tag ->
|
||||||
|
when (tag) {
|
||||||
|
"settings" -> {
|
||||||
|
navigator.navigate(Screen.SETTINGS, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.HelpEditBook(viewModel: HelpViewModel, navigator: Navigator) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
HelpItem(
|
||||||
|
title = stringResource(id = R.string.help_title_how_to_edit_book),
|
||||||
|
description = buildAnnotatedString {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_edit_book_1) + " ")
|
||||||
|
|
||||||
|
HelpAnnotation(tag = "library") {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_edit_book_2))
|
||||||
|
}
|
||||||
|
append(" ")
|
||||||
|
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_edit_book_3))
|
||||||
|
},
|
||||||
|
tags = listOf("library"),
|
||||||
|
shouldShowDescription = state.showHelpItem5,
|
||||||
|
onTitleClick = {
|
||||||
|
viewModel.onUpdate {
|
||||||
|
it.copy(
|
||||||
|
showHelpItem5 = !it.showHelpItem5
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTagClick = { tag ->
|
||||||
|
when (tag) {
|
||||||
|
"library" -> {
|
||||||
|
navigator.navigate(Screen.LIBRARY, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.foundation.text.ClickableText
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringArrayResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.ui.SlidingTransition
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.HelpFindBooksItem(viewModel: HelpViewModel) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val sites = stringArrayResource(id = R.array.book_sites)
|
||||||
|
val text = buildAnnotatedString {
|
||||||
|
append(stringResource(id = R.string.help_field_support) + " ")
|
||||||
|
|
||||||
|
sites.forEachIndexed { index, string ->
|
||||||
|
HelpAnnotation(tag = string) {
|
||||||
|
append(string.substringBeforeLast("|"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < sites.lastIndex) {
|
||||||
|
append(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HelpItem(
|
||||||
|
title = stringResource(id = R.string.help_title_how_to_find_books),
|
||||||
|
description = buildAnnotatedString {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_find_books))
|
||||||
|
},
|
||||||
|
tags = emptyList(),
|
||||||
|
shouldShowDescription = state.showHelpItem1,
|
||||||
|
onTitleClick = {
|
||||||
|
viewModel.onUpdate {
|
||||||
|
it.copy(
|
||||||
|
showHelpItem1 = !it.showHelpItem1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customContent = {
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
OutlinedTextField(
|
||||||
|
value = state.textFieldValue,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
onValueChange = { value ->
|
||||||
|
viewModel.onUpdate {
|
||||||
|
it.copy(
|
||||||
|
textFieldValue = value,
|
||||||
|
showError = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(id = R.string.help_field_placeholder))
|
||||||
|
},
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onSearch = {
|
||||||
|
viewModel.onEvent(
|
||||||
|
HelpEvent.OnSearchInWeb(
|
||||||
|
context = context,
|
||||||
|
noAppsFound = {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.error_no_browser),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
KeyboardCapitalization.Words,
|
||||||
|
imeAction = ImeAction.Search
|
||||||
|
),
|
||||||
|
trailingIcon = {
|
||||||
|
CustomIconButton(
|
||||||
|
icon = Icons.Default.Search,
|
||||||
|
contentDescription = R.string.search_content_desc,
|
||||||
|
disableOnClick = false,
|
||||||
|
enabled = !state.showError,
|
||||||
|
color = if (state.showError) {
|
||||||
|
MaterialTheme.colorScheme.outline
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
viewModel.onEvent(
|
||||||
|
HelpEvent.OnSearchInWeb(
|
||||||
|
context = context,
|
||||||
|
noAppsFound = {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.error_no_browser),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
supportingText = {
|
||||||
|
Column {
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
Box {
|
||||||
|
SlidingTransition(visible = state.showError) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.help_field_error),
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
SlidingTransition(visible = !state.showError) {
|
||||||
|
ClickableText(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodySmall.copy(
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
) { offset ->
|
||||||
|
sites.forEach { tag ->
|
||||||
|
text.getStringAnnotations(
|
||||||
|
tag = tag,
|
||||||
|
start = offset,
|
||||||
|
end = offset
|
||||||
|
).firstOrNull()?.let {
|
||||||
|
viewModel.onEvent(
|
||||||
|
HelpEvent.OnNavigateToBrowserPage(
|
||||||
|
page = tag.substringAfterLast("|"),
|
||||||
|
context = context,
|
||||||
|
noAppsFound = {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.error_no_browser),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onTagClick = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.HelpMoveOrDeleteBooks(viewModel: HelpViewModel, navigator: Navigator) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
HelpItem(
|
||||||
|
title = stringResource(id = R.string.help_title_how_to_move_or_delete_books),
|
||||||
|
description = buildAnnotatedString {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_move_or_delete_books_1) + " ")
|
||||||
|
|
||||||
|
HelpAnnotation(tag = "library") {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_move_or_delete_books_2))
|
||||||
|
}
|
||||||
|
append(". ")
|
||||||
|
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_move_or_delete_books_3))
|
||||||
|
},
|
||||||
|
tags = listOf("library"),
|
||||||
|
shouldShowDescription = state.showHelpItem4,
|
||||||
|
onTitleClick = {
|
||||||
|
viewModel.onUpdate {
|
||||||
|
it.copy(
|
||||||
|
showHelpItem4 = !it.showHelpItem4
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTagClick = { tag ->
|
||||||
|
when (tag) {
|
||||||
|
"library" -> {
|
||||||
|
navigator.navigate(Screen.LIBRARY, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.HelpReadBook(viewModel: HelpViewModel, navigator: Navigator) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
HelpItem(
|
||||||
|
title = stringResource(id = R.string.help_title_how_to_read_book),
|
||||||
|
description = buildAnnotatedString {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_read_book_1) + " ")
|
||||||
|
|
||||||
|
HelpAnnotation(tag = "library") {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_read_book_2))
|
||||||
|
}
|
||||||
|
append(". ")
|
||||||
|
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_read_book_3))
|
||||||
|
},
|
||||||
|
tags = listOf("library"),
|
||||||
|
shouldShowDescription = state.showHelpItem6,
|
||||||
|
onTitleClick = {
|
||||||
|
viewModel.onUpdate {
|
||||||
|
it.copy(
|
||||||
|
showHelpItem6 = !it.showHelpItem6
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTagClick = { tag ->
|
||||||
|
when (tag) {
|
||||||
|
"library" -> {
|
||||||
|
navigator.navigate(Screen.LIBRARY, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.HelpUpdateBook(viewModel: HelpViewModel, navigator: Navigator) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
HelpItem(
|
||||||
|
title = stringResource(id = R.string.help_title_how_to_update_book),
|
||||||
|
description = buildAnnotatedString {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_update_book_1) + " ")
|
||||||
|
|
||||||
|
HelpAnnotation(tag = "library") {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_update_book_2))
|
||||||
|
}
|
||||||
|
append(" ")
|
||||||
|
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_update_book_3))
|
||||||
|
},
|
||||||
|
tags = listOf("library"),
|
||||||
|
shouldShowDescription = state.showHelpItem8,
|
||||||
|
onTitleClick = {
|
||||||
|
viewModel.onUpdate {
|
||||||
|
it.copy(
|
||||||
|
showHelpItem8 = !it.showHelpItem8
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTagClick = { tag ->
|
||||||
|
when (tag) {
|
||||||
|
"library" -> {
|
||||||
|
navigator.navigate(Screen.LIBRARY, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,11 @@ package ua.acclorite.book_story.presentation.screens.help.data
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
||||||
sealed class HelpEvent {
|
sealed class HelpEvent {
|
||||||
|
data class OnSearchInWeb(
|
||||||
|
val context: Context,
|
||||||
|
val noAppsFound: () -> Unit
|
||||||
|
) : HelpEvent()
|
||||||
|
|
||||||
data class OnNavigateToBrowserPage(
|
data class OnNavigateToBrowserPage(
|
||||||
val page: String,
|
val page: String,
|
||||||
val context: Context,
|
val context: Context,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.data
|
||||||
|
|
||||||
|
data class HelpState(
|
||||||
|
val showNote1: Boolean = true,
|
||||||
|
|
||||||
|
val showHelpItem1: Boolean = false,
|
||||||
|
val textFieldValue: String = "",
|
||||||
|
val showError: Boolean = false,
|
||||||
|
|
||||||
|
val showHelpItem2: Boolean = false,
|
||||||
|
val showHelpItem3: Boolean = false,
|
||||||
|
val showHelpItem4: Boolean = false,
|
||||||
|
val showHelpItem5: Boolean = false,
|
||||||
|
val showHelpItem6: Boolean = false,
|
||||||
|
val showHelpItem7: Boolean = false,
|
||||||
|
val showHelpItem8: Boolean = false,
|
||||||
|
)
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.help.data
|
package ua.acclorite.book_story.presentation.screens.help.data
|
||||||
|
|
||||||
|
import android.app.SearchManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -13,6 +17,9 @@ class HelpViewModel @Inject constructor(
|
||||||
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _state = MutableStateFlow(HelpState())
|
||||||
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
fun onEvent(event: HelpEvent) {
|
fun onEvent(event: HelpEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is HelpEvent.OnNavigateToBrowserPage -> {
|
is HelpEvent.OnNavigateToBrowserPage -> {
|
||||||
|
|
@ -30,6 +37,40 @@ class HelpViewModel @Inject constructor(
|
||||||
event.noAppsFound()
|
event.noAppsFound()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is HelpEvent.OnSearchInWeb -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (_state.value.textFieldValue.isBlank()) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
showError = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val intent = Intent()
|
||||||
|
|
||||||
|
intent.action = Intent.ACTION_WEB_SEARCH
|
||||||
|
intent.putExtra(
|
||||||
|
SearchManager.QUERY,
|
||||||
|
"${_state.value.textFieldValue.trim()} filetype:txt OR filetype:pdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (intent.resolveActivity(event.context.packageManager) != null) {
|
||||||
|
event.context.startActivity(intent)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
event.noAppsFound()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onUpdate(calculation: (HelpState) -> HelpState) {
|
||||||
|
_state.update {
|
||||||
|
calculation(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
|
@ -68,7 +67,7 @@ import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalMaterial3Api::class,
|
ExperimentalMaterial3Api::class,
|
||||||
ExperimentalMaterialApi::class, ExperimentalFoundationApi::class
|
ExperimentalMaterialApi::class
|
||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryScreen(
|
fun HistoryScreen(
|
||||||
|
|
@ -214,7 +213,7 @@ fun HistoryScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
CategoryTitle(
|
CategoryTitle(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
title = when (groupedHistory.title) {
|
title = when (groupedHistory.title) {
|
||||||
"today" -> stringResource(id = R.string.today)
|
"today" -> stringResource(id = R.string.today)
|
||||||
"yesterday" -> stringResource(id = R.string.yesterday)
|
"yesterday" -> stringResource(id = R.string.yesterday)
|
||||||
|
|
@ -230,7 +229,7 @@ fun HistoryScreen(
|
||||||
groupedHistory.history, key = { it.id }
|
groupedHistory.history, key = { it.id }
|
||||||
) {
|
) {
|
||||||
HistoryItem(
|
HistoryItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
history = it,
|
history = it,
|
||||||
isOnClickEnabled = !state.isRefreshing,
|
isOnClickEnabled = !state.isRefreshing,
|
||||||
onBodyClick = {
|
onBodyClick = {
|
||||||
|
|
|
||||||
|
|
@ -347,6 +347,7 @@ class LibraryViewModel @Inject constructor(
|
||||||
query: String = if (_state.value.showSearch) _state.value.searchQuery else ""
|
query: String = if (_state.value.showSearch) _state.value.searchQuery else ""
|
||||||
) {
|
) {
|
||||||
val books = getBooks.execute(query).map { book -> Pair(book, false) }
|
val books = getBooks.execute(query).map { book -> Pair(book, false) }
|
||||||
|
|
||||||
val categorizedBooks = books.groupBy {
|
val categorizedBooks = books.groupBy {
|
||||||
it.first.category
|
it.first.category
|
||||||
}.toList().map {
|
}.toList().map {
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,11 @@ fun ReaderScreen(
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val canScrollForward by remember { derivedStateOf { listState.canScrollForward } }
|
val canScroll by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
listState.canScrollBackward && listState.canScrollForward
|
||||||
|
}
|
||||||
|
}
|
||||||
val nestedScrollConnection = remember {
|
val nestedScrollConnection = remember {
|
||||||
object : NestedScrollConnection {
|
object : NestedScrollConnection {
|
||||||
override fun onPostScroll(
|
override fun onPostScroll(
|
||||||
|
|
@ -129,8 +133,8 @@ fun ReaderScreen(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
LaunchedEffect(canScrollForward) {
|
LaunchedEffect(canScroll) {
|
||||||
if (!canScrollForward && !state.showMenu && !loading) {
|
if (!canScroll && !state.showMenu && !loading) {
|
||||||
viewModel.onEvent(ReaderEvent.OnShowHideMenu(context = context))
|
viewModel.onEvent(ReaderEvent.OnShowHideMenu(context = context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.MenuBook
|
import androidx.compose.material.icons.automirrored.filled.MenuBook
|
||||||
import androidx.compose.material.icons.filled.DisplaySettings
|
import androidx.compose.material.icons.filled.DisplaySettings
|
||||||
|
|
@ -26,6 +25,7 @@ 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.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
|
import ua.acclorite.book_story.presentation.components.collapsibleScrollBehaviorWithLazyListState
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.components.SettingsCategoryItem
|
import ua.acclorite.book_story.presentation.screens.settings.components.SettingsCategoryItem
|
||||||
|
|
@ -35,17 +35,12 @@ import ua.acclorite.book_story.presentation.screens.settings.components.Settings
|
||||||
fun SettingsScreen(
|
fun SettingsScreen(
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyListState()
|
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
|
||||||
canScroll = {
|
|
||||||
listState.canScrollForward || listState.canScrollBackward
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|
@ -56,7 +51,7 @@ fun SettingsScreen(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
GoBackButton(navigator = navigator)
|
GoBackButton(navigator = navigator)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollState.first,
|
||||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||||
|
|
@ -68,7 +63,7 @@ fun SettingsScreen(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = paddingValues.calculateTopPadding()),
|
.padding(top = paddingValues.calculateTopPadding()),
|
||||||
state = listState
|
state = scrollState.second
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ColorPickerWithTitle(
|
fun ColorPickerWithTitle(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
value: Color,
|
value: Color,
|
||||||
title: String,
|
title: String,
|
||||||
horizontalPadding: Dp = 18.dp,
|
horizontalPadding: Dp = 18.dp,
|
||||||
|
|
@ -42,7 +43,7 @@ fun ColorPickerWithTitle(
|
||||||
var color by remember { mutableStateOf(value) }
|
var color by remember { mutableStateOf(value) }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = verticalPadding, horizontal = horizontalPadding)
|
.padding(vertical = verticalPadding, horizontal = horizontalPadding)
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.SegmentedButton
|
import androidx.compose.material3.SegmentedButton
|
||||||
import androidx.compose.material3.SegmentedButtonDefaults
|
import androidx.compose.material3.SegmentedButtonDefaults
|
||||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||||
|
|
@ -19,12 +18,12 @@ import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.domain.model.ButtonItem
|
import ua.acclorite.book_story.domain.model.ButtonItem
|
||||||
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SegmentedButtonWithTitle(
|
fun SegmentedButtonWithTitle(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
title: String,
|
title: String,
|
||||||
buttons: List<ButtonItem>,
|
buttons: List<ButtonItem>,
|
||||||
enabled: Boolean,
|
locked: Boolean,
|
||||||
horizontalPadding: Dp = 18.dp,
|
horizontalPadding: Dp = 18.dp,
|
||||||
verticalPadding: Dp = 8.dp,
|
verticalPadding: Dp = 8.dp,
|
||||||
onClick: (ButtonItem) -> Unit
|
onClick: (ButtonItem) -> Unit
|
||||||
|
|
@ -32,7 +31,7 @@ fun SegmentedButtonWithTitle(
|
||||||
val colors = SegmentedButtonDefaults.colors()
|
val colors = SegmentedButtonDefaults.colors()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = horizontalPadding, vertical = verticalPadding)
|
.padding(horizontal = horizontalPadding, vertical = verticalPadding)
|
||||||
) {
|
) {
|
||||||
|
|
@ -43,7 +42,7 @@ fun SegmentedButtonWithTitle(
|
||||||
SingleChoiceSegmentedButtonRow {
|
SingleChoiceSegmentedButtonRow {
|
||||||
buttons.forEachIndexed { index, buttonItem ->
|
buttons.forEachIndexed { index, buttonItem ->
|
||||||
SegmentedButton(
|
SegmentedButton(
|
||||||
enabled = enabled,
|
enabled = locked,
|
||||||
selected = buttonItem.selected,
|
selected = buttonItem.selected,
|
||||||
onClick = { onClick(buttonItem) },
|
onClick = { onClick(buttonItem) },
|
||||||
shape = when (index) {
|
shape = when (index) {
|
||||||
|
|
@ -60,9 +59,12 @@ fun SegmentedButtonWithTitle(
|
||||||
else -> RoundedCornerShape(0)
|
else -> RoundedCornerShape(0)
|
||||||
},
|
},
|
||||||
colors = SegmentedButtonDefaults.colors(
|
colors = SegmentedButtonDefaults.colors(
|
||||||
disabledInactiveBorderColor = colors.disabledActiveBorderColor,
|
disabledInactiveBorderColor = colors.activeBorderColor,
|
||||||
disabledInactiveContentColor = colors.disabledActiveContentColor,
|
disabledInactiveContentColor = colors.inactiveContentColor,
|
||||||
disabledActiveContainerColor = colors.activeContainerColor.copy(0.12f),
|
disabledActiveContainerColor = colors.activeContainerColor,
|
||||||
|
disabledActiveContentColor = colors.activeContentColor,
|
||||||
|
disabledInactiveContainerColor = colors.inactiveContainerColor,
|
||||||
|
disabledActiveBorderColor = colors.activeBorderColor,
|
||||||
),
|
),
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.LargeTopAppBar
|
import androidx.compose.material3.LargeTopAppBar
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -16,26 +15,37 @@ import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
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 ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.ButtonItem
|
import ua.acclorite.book_story.domain.model.ButtonItem
|
||||||
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||||
import ua.acclorite.book_story.presentation.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
|
import ua.acclorite.book_story.presentation.components.collapsibleScrollBehaviorWithLazyListState
|
||||||
import ua.acclorite.book_story.presentation.data.MainEvent
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
import ua.acclorite.book_story.presentation.data.MainViewModel
|
import ua.acclorite.book_story.presentation.data.MainViewModel
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.components.ColorPickerWithTitle
|
import ua.acclorite.book_story.presentation.screens.settings.components.ColorPickerWithTitle
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.components.SegmentedButtonWithTitle
|
import ua.acclorite.book_story.presentation.screens.settings.components.SegmentedButtonWithTitle
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher.AppearanceSettingsThemeSwitcher
|
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher.AppearanceSettingsThemeSwitcher
|
||||||
|
import ua.acclorite.book_story.presentation.ui.BookStoryTheme
|
||||||
import ua.acclorite.book_story.presentation.ui.DarkTheme
|
import ua.acclorite.book_story.presentation.ui.DarkTheme
|
||||||
|
import ua.acclorite.book_story.presentation.ui.PureDark
|
||||||
|
import ua.acclorite.book_story.presentation.ui.SlidingTransition
|
||||||
import ua.acclorite.book_story.presentation.ui.Theme
|
import ua.acclorite.book_story.presentation.ui.Theme
|
||||||
import ua.acclorite.book_story.presentation.ui.ThemeContrast
|
import ua.acclorite.book_story.presentation.ui.ThemeContrast
|
||||||
|
import ua.acclorite.book_story.presentation.ui.isDark
|
||||||
|
import ua.acclorite.book_story.presentation.ui.isPureDark
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -43,19 +53,21 @@ fun AppearanceSettings(
|
||||||
mainViewModel: MainViewModel,
|
mainViewModel: MainViewModel,
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyListState()
|
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
|
||||||
canScroll = {
|
|
||||||
listState.canScrollForward || listState.canScrollBackward
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val state by mainViewModel.state.collectAsState()
|
val state by mainViewModel.state.collectAsState()
|
||||||
|
var themeContrastTheme by remember { mutableStateOf(state.theme!!) }
|
||||||
|
|
||||||
|
LaunchedEffect(state.theme) {
|
||||||
|
if (themeContrastTheme != state.theme && state.theme != Theme.DYNAMIC) {
|
||||||
|
themeContrastTheme = state.theme!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|
@ -66,7 +78,7 @@ fun AppearanceSettings(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
GoBackButton(navigator = navigator)
|
GoBackButton(navigator = navigator)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollState.first,
|
||||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||||
|
|
@ -78,25 +90,35 @@ fun AppearanceSettings(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = paddingValues.calculateTopPadding()),
|
.padding(top = paddingValues.calculateTopPadding()),
|
||||||
state = listState
|
state = scrollState.second
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.animateItem()
|
||||||
|
.height(16.dp)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
CategoryTitle(
|
CategoryTitle(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
title = stringResource(id = R.string.theme_appearance_settings),
|
title = stringResource(id = R.string.theme_appearance_settings),
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.animateItem()
|
||||||
|
.height(8.dp)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
SegmentedButtonWithTitle(
|
SegmentedButtonWithTitle(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
title = stringResource(id = R.string.dark_theme_option),
|
title = stringResource(id = R.string.dark_theme_option),
|
||||||
enabled = true,
|
locked = true,
|
||||||
buttons = DarkTheme.entries.map {
|
buttons = DarkTheme.entries.map {
|
||||||
ButtonItem(
|
ButtonItem(
|
||||||
it.toString(),
|
it.toString(),
|
||||||
|
|
@ -119,9 +141,29 @@ fun AppearanceSettings(
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
|
AppearanceSettingsThemeSwitcher(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
|
mainViewModel = mainViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
BookStoryTheme(
|
||||||
|
theme = themeContrastTheme,
|
||||||
|
isDark = state.darkTheme!!.isDark(),
|
||||||
|
isPureDark = state.pureDark!!.isPureDark(context = LocalContext.current),
|
||||||
|
themeContrast = state.themeContrast!!
|
||||||
|
) {
|
||||||
|
SlidingTransition(
|
||||||
|
modifier = Modifier.animateItem(
|
||||||
|
fadeInSpec = null,
|
||||||
|
fadeOutSpec = null
|
||||||
|
),
|
||||||
|
visible = state.theme != Theme.DYNAMIC,
|
||||||
|
) {
|
||||||
SegmentedButtonWithTitle(
|
SegmentedButtonWithTitle(
|
||||||
title = stringResource(id = R.string.theme_contrast_option),
|
title = stringResource(id = R.string.theme_contrast_option),
|
||||||
enabled = state.theme != Theme.DYNAMIC,
|
locked = state.theme != Theme.DYNAMIC,
|
||||||
buttons = ThemeContrast.entries.map {
|
buttons = ThemeContrast.entries.map {
|
||||||
ButtonItem(
|
ButtonItem(
|
||||||
it.toString(),
|
it.toString(),
|
||||||
|
|
@ -142,26 +184,63 @@ fun AppearanceSettings(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
item {
|
|
||||||
AppearanceSettingsThemeSwitcher(mainViewModel = mainViewModel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(22.dp))
|
SlidingTransition(
|
||||||
}
|
modifier = Modifier.animateItem(
|
||||||
item {
|
fadeInSpec = null,
|
||||||
CategoryTitle(
|
fadeOutSpec = null
|
||||||
title = stringResource(id = R.string.colors_appearance_settings),
|
),
|
||||||
color = MaterialTheme.colorScheme.primary
|
visible = state.darkTheme!!.isDark(),
|
||||||
|
) {
|
||||||
|
SegmentedButtonWithTitle(
|
||||||
|
title = stringResource(id = R.string.pure_dark_option),
|
||||||
|
locked = true,
|
||||||
|
buttons = PureDark.entries.map {
|
||||||
|
ButtonItem(
|
||||||
|
it.toString(),
|
||||||
|
when (it) {
|
||||||
|
PureDark.OFF -> stringResource(id = R.string.pure_dark_off)
|
||||||
|
PureDark.ON -> stringResource(id = R.string.pure_dark_on)
|
||||||
|
PureDark.SAVER -> stringResource(id = R.string.pure_dark_power_saver)
|
||||||
|
},
|
||||||
|
MaterialTheme.typography.labelLarge,
|
||||||
|
it == state.pureDark
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
mainViewModel.onEvent(
|
||||||
|
MainEvent.OnChangePureDark(
|
||||||
|
it.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.animateItem()
|
||||||
|
.height(22.dp)
|
||||||
|
)
|
||||||
|
CategoryTitle(
|
||||||
|
title = stringResource(id = R.string.colors_appearance_settings),
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.animateItem()
|
||||||
|
)
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.animateItem()
|
||||||
|
.height(8.dp)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
ColorPickerWithTitle(
|
ColorPickerWithTitle(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
value = Color(state.backgroundColor!!.toULong()),
|
value = Color(state.backgroundColor!!.toULong()),
|
||||||
title = stringResource(id = R.string.background_color_option),
|
title = stringResource(id = R.string.background_color_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
|
|
@ -175,6 +254,7 @@ fun AppearanceSettings(
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ColorPickerWithTitle(
|
ColorPickerWithTitle(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
value = Color(state.fontColor!!.toULong()),
|
value = Color(state.fontColor!!.toULong()),
|
||||||
title = stringResource(id = R.string.font_color_option),
|
title = stringResource(id = R.string.font_color_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
|
|
@ -187,7 +267,13 @@ fun AppearanceSettings(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item { Spacer(modifier = Modifier.height(48.dp)) }
|
item {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.animateItem()
|
||||||
|
.height(48.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
|
@ -13,7 +14,9 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
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.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.util.Constants
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
|
|
@ -22,13 +25,16 @@ import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
import ua.acclorite.book_story.presentation.data.MainViewModel
|
import ua.acclorite.book_story.presentation.data.MainViewModel
|
||||||
import ua.acclorite.book_story.presentation.ui.Theme
|
import ua.acclorite.book_story.presentation.ui.Theme
|
||||||
import ua.acclorite.book_story.presentation.ui.isDark
|
import ua.acclorite.book_story.presentation.ui.isDark
|
||||||
|
import ua.acclorite.book_story.presentation.ui.isPureDark
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theme switcher.
|
* Theme switcher.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun AppearanceSettingsThemeSwitcher(
|
fun AppearanceSettingsThemeSwitcher(
|
||||||
mainViewModel: MainViewModel
|
modifier: Modifier = Modifier,
|
||||||
|
mainViewModel: MainViewModel,
|
||||||
|
verticalPadding: Dp = 8.dp
|
||||||
) {
|
) {
|
||||||
val state by mainViewModel.state.collectAsState()
|
val state by mainViewModel.state.collectAsState()
|
||||||
val themes = remember {
|
val themes = remember {
|
||||||
|
|
@ -37,10 +43,10 @@ fun AppearanceSettingsThemeSwitcher(
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = verticalPadding)
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
CategoryTitle(
|
CategoryTitle(
|
||||||
title = stringResource(id = R.string.app_theme_option)
|
title = stringResource(id = R.string.app_theme_option)
|
||||||
)
|
)
|
||||||
|
|
@ -60,6 +66,7 @@ fun AppearanceSettingsThemeSwitcher(
|
||||||
theme = themeEntry,
|
theme = themeEntry,
|
||||||
darkTheme = state.darkTheme!!.isDark(),
|
darkTheme = state.darkTheme!!.isDark(),
|
||||||
themeContrast = state.themeContrast!!,
|
themeContrast = state.themeContrast!!,
|
||||||
|
isPureDark = state.pureDark!!.isPureDark(context = LocalContext.current),
|
||||||
selected = state.theme == themeEntry.first
|
selected = state.theme == themeEntry.first
|
||||||
) {
|
) {
|
||||||
mainViewModel.onEvent(MainEvent.OnChangeTheme(themeEntry.first.toString()))
|
mainViewModel.onEvent(MainEvent.OnChangeTheme(themeEntry.first.toString()))
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,17 @@ import ua.acclorite.book_story.presentation.ui.animatedColorScheme
|
||||||
fun AppearanceSettingsThemeSwitcherItem(
|
fun AppearanceSettingsThemeSwitcherItem(
|
||||||
theme: Pair<Theme, UIText>,
|
theme: Pair<Theme, UIText>,
|
||||||
darkTheme: Boolean,
|
darkTheme: Boolean,
|
||||||
|
isPureDark: Boolean,
|
||||||
themeContrast: ThemeContrast,
|
themeContrast: ThemeContrast,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
val colorScheme = animatedColorScheme(theme.first, darkTheme, themeContrast)
|
val colorScheme = animatedColorScheme(
|
||||||
|
theme.first,
|
||||||
|
darkTheme,
|
||||||
|
isPureDark,
|
||||||
|
themeContrast
|
||||||
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.LargeTopAppBar
|
import androidx.compose.material3.LargeTopAppBar
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -26,6 +25,7 @@ import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.ButtonItem
|
import ua.acclorite.book_story.domain.model.ButtonItem
|
||||||
import ua.acclorite.book_story.domain.util.Constants
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
import ua.acclorite.book_story.presentation.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
|
import ua.acclorite.book_story.presentation.components.collapsibleScrollBehaviorWithLazyListState
|
||||||
import ua.acclorite.book_story.presentation.data.MainEvent
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
import ua.acclorite.book_story.presentation.data.MainViewModel
|
import ua.acclorite.book_story.presentation.data.MainViewModel
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
|
@ -37,19 +37,13 @@ fun GeneralSettings(
|
||||||
mainViewModel: MainViewModel,
|
mainViewModel: MainViewModel,
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyListState()
|
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
|
||||||
canScroll = {
|
|
||||||
listState.canScrollForward || listState.canScrollBackward
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val state by mainViewModel.state.collectAsState()
|
val state by mainViewModel.state.collectAsState()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|
@ -60,7 +54,7 @@ fun GeneralSettings(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
GoBackButton(navigator = navigator)
|
GoBackButton(navigator = navigator)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollState.first,
|
||||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||||
|
|
@ -72,7 +66,7 @@ fun GeneralSettings(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = paddingValues.calculateTopPadding()),
|
.padding(top = paddingValues.calculateTopPadding()),
|
||||||
state = listState
|
state = scrollState.second
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.LargeTopAppBar
|
import androidx.compose.material3.LargeTopAppBar
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -29,6 +28,7 @@ import ua.acclorite.book_story.domain.model.ButtonItem
|
||||||
import ua.acclorite.book_story.domain.util.Constants
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||||
import ua.acclorite.book_story.presentation.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
|
import ua.acclorite.book_story.presentation.components.collapsibleScrollBehaviorWithLazyListState
|
||||||
import ua.acclorite.book_story.presentation.data.MainEvent
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
import ua.acclorite.book_story.presentation.data.MainViewModel
|
import ua.acclorite.book_story.presentation.data.MainViewModel
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
|
@ -42,12 +42,7 @@ fun ReaderSettings(
|
||||||
mainViewModel: MainViewModel,
|
mainViewModel: MainViewModel,
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyListState()
|
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
|
||||||
canScroll = {
|
|
||||||
listState.canScrollForward || listState.canScrollBackward
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val state by mainViewModel.state.collectAsState()
|
val state by mainViewModel.state.collectAsState()
|
||||||
val fontFamily = remember(state.fontFamily) {
|
val fontFamily = remember(state.fontFamily) {
|
||||||
|
|
@ -59,7 +54,7 @@ fun ReaderSettings(
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|
@ -70,7 +65,7 @@ fun ReaderSettings(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
GoBackButton(navigator = navigator)
|
GoBackButton(navigator = navigator)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollState.first,
|
||||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||||
|
|
@ -82,7 +77,7 @@ fun ReaderSettings(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = paddingValues.calculateTopPadding()),
|
.padding(top = paddingValues.calculateTopPadding()),
|
||||||
state = listState
|
state = scrollState.second
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import ua.acclorite.book_story.presentation.ui.theme.aquaTheme
|
import ua.acclorite.book_story.presentation.ui.theme.aquaTheme
|
||||||
|
import ua.acclorite.book_story.presentation.ui.theme.blackTheme
|
||||||
import ua.acclorite.book_story.presentation.ui.theme.blueTheme
|
import ua.acclorite.book_story.presentation.ui.theme.blueTheme
|
||||||
import ua.acclorite.book_story.presentation.ui.theme.greenTheme
|
import ua.acclorite.book_story.presentation.ui.theme.greenTheme
|
||||||
import ua.acclorite.book_story.presentation.ui.theme.pinkTheme
|
import ua.acclorite.book_story.presentation.ui.theme.pinkTheme
|
||||||
|
|
@ -43,49 +44,61 @@ fun String.toTheme(): Theme {
|
||||||
*/
|
*/
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
@Composable
|
@Composable
|
||||||
fun colorScheme(theme: Theme, darkTheme: Boolean, themeContrast: ThemeContrast): ColorScheme {
|
fun colorScheme(
|
||||||
when (theme) {
|
theme: Theme,
|
||||||
|
darkTheme: Boolean,
|
||||||
|
isPureDark: Boolean,
|
||||||
|
themeContrast: ThemeContrast
|
||||||
|
): ColorScheme {
|
||||||
|
val colorScheme = when (theme) {
|
||||||
Theme.DYNAMIC -> {
|
Theme.DYNAMIC -> {
|
||||||
/* Dynamic Theme */
|
/* Dynamic Theme */
|
||||||
return if (darkTheme)
|
if (darkTheme) {
|
||||||
dynamicDarkColorScheme(LocalContext.current)
|
dynamicDarkColorScheme(LocalContext.current)
|
||||||
else
|
} else {
|
||||||
dynamicLightColorScheme(LocalContext.current)
|
dynamicLightColorScheme(LocalContext.current)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Theme.BLUE -> {
|
Theme.BLUE -> {
|
||||||
/* Blue Theme */
|
/* Blue Theme */
|
||||||
return blueTheme(isDark = darkTheme, themeContrast = themeContrast)
|
blueTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||||
}
|
}
|
||||||
|
|
||||||
Theme.PURPLE -> {
|
Theme.PURPLE -> {
|
||||||
/* Purple Theme */
|
/* Purple Theme */
|
||||||
return purpleTheme(isDark = darkTheme, themeContrast = themeContrast)
|
purpleTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||||
}
|
}
|
||||||
|
|
||||||
Theme.GREEN -> {
|
Theme.GREEN -> {
|
||||||
/* Green Theme */
|
/* Green Theme */
|
||||||
return greenTheme(isDark = darkTheme, themeContrast = themeContrast)
|
greenTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||||
}
|
}
|
||||||
|
|
||||||
Theme.PINK -> {
|
Theme.PINK -> {
|
||||||
/* Pink Theme */
|
/* Pink Theme */
|
||||||
return pinkTheme(isDark = darkTheme, themeContrast = themeContrast)
|
pinkTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||||
}
|
}
|
||||||
|
|
||||||
Theme.YELLOW -> {
|
Theme.YELLOW -> {
|
||||||
/* Yellow Theme */
|
/* Yellow Theme */
|
||||||
return yellowTheme(isDark = darkTheme, themeContrast = themeContrast)
|
yellowTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||||
}
|
}
|
||||||
|
|
||||||
Theme.RED -> {
|
Theme.RED -> {
|
||||||
/* Red Theme */
|
/* Red Theme */
|
||||||
return redTheme(isDark = darkTheme, themeContrast = themeContrast)
|
redTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||||
}
|
}
|
||||||
|
|
||||||
Theme.AQUA -> {
|
Theme.AQUA -> {
|
||||||
/* Aqua Theme */
|
/* Aqua Theme */
|
||||||
return aquaTheme(isDark = darkTheme, themeContrast = themeContrast)
|
aquaTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return if (isPureDark && darkTheme) {
|
||||||
|
blackTheme(initialTheme = colorScheme)
|
||||||
|
} else {
|
||||||
|
colorScheme
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package ua.acclorite.book_story.presentation.ui
|
package ua.acclorite.book_story.presentation.ui
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.PowerManager
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
|
@ -16,6 +18,13 @@ import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
enum class PureDark {
|
||||||
|
OFF,
|
||||||
|
ON,
|
||||||
|
SAVER
|
||||||
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
enum class ThemeContrast {
|
enum class ThemeContrast {
|
||||||
STANDARD,
|
STANDARD,
|
||||||
|
|
@ -34,6 +43,10 @@ fun String.toDarkTheme(): DarkTheme {
|
||||||
return DarkTheme.valueOf(this)
|
return DarkTheme.valueOf(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.toPureDark(): PureDark {
|
||||||
|
return PureDark.valueOf(this)
|
||||||
|
}
|
||||||
|
|
||||||
fun String.toThemeContrast(): ThemeContrast {
|
fun String.toThemeContrast(): ThemeContrast {
|
||||||
return ThemeContrast.valueOf(this)
|
return ThemeContrast.valueOf(this)
|
||||||
}
|
}
|
||||||
|
|
@ -48,9 +61,21 @@ fun DarkTheme.isDark(): Boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BooksHistoryResurrectionTheme(
|
fun PureDark.isPureDark(context: Context): Boolean {
|
||||||
|
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
|
||||||
|
return when (this) {
|
||||||
|
PureDark.OFF -> false
|
||||||
|
PureDark.ON -> true
|
||||||
|
PureDark.SAVER -> powerManager.isPowerSaveMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BookStoryTheme(
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
isDark: Boolean,
|
isDark: Boolean,
|
||||||
|
isPureDark: Boolean,
|
||||||
themeContrast: ThemeContrast,
|
themeContrast: ThemeContrast,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
@ -76,6 +101,7 @@ fun BooksHistoryResurrectionTheme(
|
||||||
theme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) theme
|
theme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) theme
|
||||||
else if (theme != Theme.DYNAMIC) theme else Theme.BLUE,
|
else if (theme != Theme.DYNAMIC) theme else Theme.BLUE,
|
||||||
darkTheme = isDark,
|
darkTheme = isDark,
|
||||||
|
isPureDark = isPureDark,
|
||||||
themeContrast = themeContrast
|
themeContrast = themeContrast
|
||||||
)
|
)
|
||||||
val animatedColorScheme = animateColorScheme(targetColorScheme = colorScheme)
|
val animatedColorScheme = animateColorScheme(targetColorScheme = colorScheme)
|
||||||
|
|
@ -143,11 +169,13 @@ private fun animateColorScheme(targetColorScheme: ColorScheme): ColorScheme {
|
||||||
fun animatedColorScheme(
|
fun animatedColorScheme(
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
isDark: Boolean,
|
isDark: Boolean,
|
||||||
|
isPureDark: Boolean,
|
||||||
themeContrast: ThemeContrast
|
themeContrast: ThemeContrast
|
||||||
): ColorScheme {
|
): ColorScheme {
|
||||||
val colorScheme = colorScheme(
|
val colorScheme = colorScheme(
|
||||||
theme = theme,
|
theme = theme,
|
||||||
darkTheme = isDark,
|
darkTheme = isDark,
|
||||||
|
isPureDark = isPureDark,
|
||||||
themeContrast = themeContrast
|
themeContrast = themeContrast
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.slideInHorizontally
|
import androidx.compose.animation.slideInHorizontally
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutHorizontally
|
import androidx.compose.animation.slideOutHorizontally
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
|
@ -13,20 +15,20 @@ object Transitions {
|
||||||
val DefaultTransitionIn = fadeIn(tween(300))
|
val DefaultTransitionIn = fadeIn(tween(300))
|
||||||
val DefaultTransitionOut = fadeOut(tween(300))
|
val DefaultTransitionOut = fadeOut(tween(300))
|
||||||
|
|
||||||
val FadeTransitionIn = fadeIn(tween(300))
|
val FadeTransitionIn = fadeIn(tween(350))
|
||||||
val FadeTransitionOut = fadeOut(tween(50))
|
val FadeTransitionOut = fadeOut(tween(100))
|
||||||
|
|
||||||
val SlidingTransitionIn = fadeIn(tween(300)) +
|
val SlidingTransitionIn = fadeIn(tween(350)) +
|
||||||
slideInHorizontally(tween(300)) { it / 16 }
|
slideInHorizontally(tween(350)) { it / 16 }
|
||||||
|
|
||||||
val BackSlidingTransitionIn = fadeIn(tween(300)) +
|
val BackSlidingTransitionIn = fadeIn(tween(350)) +
|
||||||
slideInHorizontally(tween(300)) { -it / 16 }
|
slideInHorizontally(tween(350)) { -it / 16 }
|
||||||
|
|
||||||
val SlidingTransitionOut = fadeOut(tween(200)) +
|
val SlidingTransitionOut = fadeOut(tween(250)) +
|
||||||
slideOutHorizontally(tween(300)) { -it / 16 }
|
slideOutHorizontally(tween(350)) { -it / 16 }
|
||||||
|
|
||||||
val BackSlidingTransitionOut = fadeOut(tween(200)) +
|
val BackSlidingTransitionOut = fadeOut(tween(250)) +
|
||||||
slideOutHorizontally(tween(300)) { it / 16 }
|
slideOutHorizontally(tween(350)) { it / 16 }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -45,6 +47,24 @@ fun DefaultTransition(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SlidingTransition(
|
||||||
|
visible: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = visible,
|
||||||
|
modifier = modifier,
|
||||||
|
enter = slideInVertically(tween(300)) { -it / 10 } +
|
||||||
|
fadeIn(tween(300)),
|
||||||
|
exit = slideOutVertically(tween(150)) { -it / 10 } +
|
||||||
|
fadeOut(tween(100)),
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package ua.acclorite.book_story.presentation.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material3.ColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun blackTheme(initialTheme: ColorScheme): ColorScheme {
|
||||||
|
return initialTheme.copy(
|
||||||
|
surface = initialTheme.surface.copy {
|
||||||
|
val darker = 3f
|
||||||
|
it.copy(
|
||||||
|
red = it.red / darker,
|
||||||
|
green = it.green / darker,
|
||||||
|
blue = it.blue / darker
|
||||||
|
)
|
||||||
|
},
|
||||||
|
surfaceContainer = initialTheme.surfaceContainer.copy {
|
||||||
|
val darker = 1.95f
|
||||||
|
it.copy(
|
||||||
|
red = it.red / darker,
|
||||||
|
green = it.green / darker,
|
||||||
|
blue = it.blue / darker
|
||||||
|
)
|
||||||
|
},
|
||||||
|
surfaceContainerLowest = initialTheme.surfaceContainerLowest.copy {
|
||||||
|
val darker = 1.95f
|
||||||
|
it.copy(
|
||||||
|
red = it.red / darker,
|
||||||
|
green = it.green / darker,
|
||||||
|
blue = it.blue / darker
|
||||||
|
)
|
||||||
|
},
|
||||||
|
surfaceContainerLow = initialTheme.surfaceContainerLow.copy {
|
||||||
|
val darker = 1.95f
|
||||||
|
it.copy(
|
||||||
|
red = it.red / darker,
|
||||||
|
green = it.green / darker,
|
||||||
|
blue = it.blue / darker
|
||||||
|
)
|
||||||
|
},
|
||||||
|
surfaceContainerHigh = initialTheme.surfaceContainerHigh.copy {
|
||||||
|
val darker = 1.95f
|
||||||
|
it.copy(
|
||||||
|
red = it.red / darker,
|
||||||
|
green = it.green / darker,
|
||||||
|
blue = it.blue / darker
|
||||||
|
)
|
||||||
|
},
|
||||||
|
surfaceContainerHighest = initialTheme.surfaceContainerHighest.copy {
|
||||||
|
val darker = 1.95f
|
||||||
|
it.copy(
|
||||||
|
red = it.red / darker,
|
||||||
|
green = it.green / darker,
|
||||||
|
blue = it.blue / darker
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Color.copy(calculation: (Color) -> Color): Color {
|
||||||
|
return calculation(this)
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<style name="Theme.Start.Splash" parent="Theme.SplashScreen">
|
<style name="Theme.Start.Splash" parent="Theme.SplashScreen">
|
||||||
<item name="android:forceDarkAllowed" tools:ignore="NewApi">false</item>
|
<item name="android:forceDarkAllowed" tools:ignore="NewApi">false</item>
|
||||||
<item name="postSplashScreenTheme">@style/Theme.BooksHistoryResurrection</item>
|
<item name="postSplashScreenTheme">@style/Theme.BookStory</item>
|
||||||
|
|
||||||
<item name="windowSplashScreenBackground">@color/splash_bg_dark</item>
|
<item name="windowSplashScreenBackground">@color/splash_bg_dark</item>
|
||||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!-- Basic Strings -->
|
<!-- Basic Strings -->
|
||||||
<string name="app_name">Історія Книги</string>
|
<string name="app_name">Історія Книги</string>
|
||||||
|
|
||||||
|
<!-- Book sources -->
|
||||||
|
<string-array name="book_sites" tools:ignore="InconsistentArrays">
|
||||||
|
<item>Укрліб|https://www.ukrlib.com.ua</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<!-- Screens -->
|
<!-- Screens -->
|
||||||
<string name="library_screen">Бібліотека</string>
|
<string name="library_screen">Бібліотека</string>
|
||||||
<string name="history_screen">Історія</string>
|
<string name="history_screen">Історія</string>
|
||||||
|
|
@ -108,7 +113,7 @@
|
||||||
<string name="continue_read">Продовжити</string>
|
<string name="continue_read">Продовжити</string>
|
||||||
<string name="never">Ніколи</string>
|
<string name="never">Ніколи</string>
|
||||||
<string name="go_back">Назад</string>
|
<string name="go_back">Назад</string>
|
||||||
<string name="move_to_read">Перемістити цю книгу в \"Прочитано\"</string>
|
<string name="move_to_read">Перемістити цю книгу в «Прочитано»</string>
|
||||||
<string name="back_to_library">Назад в Бібліотеку</string>
|
<string name="back_to_library">Назад в Бібліотеку</string>
|
||||||
<string name="undo">Скасувати</string>
|
<string name="undo">Скасувати</string>
|
||||||
<string name="copy">Копіювати</string>
|
<string name="copy">Копіювати</string>
|
||||||
|
|
@ -160,6 +165,7 @@
|
||||||
<!-- Settings options -->
|
<!-- Settings options -->
|
||||||
<string name="language_option">Мова застосунка</string>
|
<string name="language_option">Мова застосунка</string>
|
||||||
<string name="dark_theme_option">Темна тема</string>
|
<string name="dark_theme_option">Темна тема</string>
|
||||||
|
<string name="pure_dark_option">Суцільна темрява (ОЛЕД)</string>
|
||||||
<string name="theme_contrast_option">Контрастність теми</string>
|
<string name="theme_contrast_option">Контрастність теми</string>
|
||||||
<string name="app_theme_option">Тема застосунка</string>
|
<string name="app_theme_option">Тема застосунка</string>
|
||||||
<string name="font_family_option">Сімейство шрифтів</string>
|
<string name="font_family_option">Сімейство шрифтів</string>
|
||||||
|
|
@ -181,6 +187,11 @@
|
||||||
<string name="dark_theme_on">Ввімкнена</string>
|
<string name="dark_theme_on">Ввімкнена</string>
|
||||||
<string name="dark_theme_follow_system">Системна</string>
|
<string name="dark_theme_follow_system">Системна</string>
|
||||||
|
|
||||||
|
<!-- Pure Dark properties -->
|
||||||
|
<string name="pure_dark_off">Вимкнена</string>
|
||||||
|
<string name="pure_dark_on">Ввімкнена</string>
|
||||||
|
<string name="pure_dark_power_saver">Економія</string>
|
||||||
|
|
||||||
<!-- Theme Contrast properties -->
|
<!-- Theme Contrast properties -->
|
||||||
<string name="theme_contrast_standard">Типова</string>
|
<string name="theme_contrast_standard">Типова</string>
|
||||||
<string name="theme_contrast_medium">Середня</string>
|
<string name="theme_contrast_medium">Середня</string>
|
||||||
|
|
@ -257,10 +268,106 @@
|
||||||
<string name="slava_ukraini_option_desc">Героям Слава! 🐉</string>
|
<string name="slava_ukraini_option_desc">Героям Слава! 🐉</string>
|
||||||
|
|
||||||
<!-- Help -->
|
<!-- Help -->
|
||||||
<string name="clickable_note_1">Будь ласка, зверніть увагу, що на</string>
|
<string name="help_clickable_note_1">Будь ласка, зверніть увагу, що на</string>
|
||||||
<string name="clickable_note_2">такий виділений текст</string>
|
<string name="help_clickable_note_2">такий виділений текст</string>
|
||||||
<string name="clickable_note_3">можна натиснути.</string>
|
<string name="help_clickable_note_3">
|
||||||
<string name="clickable_note_action">На мене можна натиснути!</string>
|
можна натиснути.
|
||||||
|
Також ви можете приховати цю примітку, натиснувши на іконку, спробуйте!
|
||||||
|
</string>
|
||||||
|
<string name="help_clickable_note_action">На мене можна натиснути!</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_find_books">Як мені знайти та завантажити книги?</string>
|
||||||
|
<string name="help_desc_how_to_find_books">
|
||||||
|
Ви можете спробувати знайти бажані книги використовуючи сайти з наведеної примітки
|
||||||
|
або ви можете використати текстове поле, щоб шукати книги в інтернеті.
|
||||||
|
</string>
|
||||||
|
<string name="help_field_placeholder">Назва книги</string>
|
||||||
|
<string name="help_field_support">Корисні сайти з книгами:</string>
|
||||||
|
<string name="help_field_error">Назва книги не має бути порожньою</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_add_books">Як мені додати книги в Бібліотеку?</string>
|
||||||
|
<string name="help_desc_how_to_add_books_1">Перейдіть в</string>
|
||||||
|
<string name="help_desc_how_to_add_books_2">Огляд</string>
|
||||||
|
<string name="help_desc_how_to_add_books_3">
|
||||||
|
Якщо ви не бачите завантажені книги, спробуйте оновити список, потягнувши його вниз.
|
||||||
|
Переконайтеся, що ваші книги має підтримуваний формат файлу.
|
||||||
|
Потім натисніть на книгу, щоб вибрати її, або утримуйте, щоб показати її місцезнаходження.
|
||||||
|
Після того, як ви вибрали всі потрібні книги, натисніть на іконку з галочкою у верхньому правому куті,
|
||||||
|
зачекайте поки всі книги завантажаться, і натисніть «Додати».
|
||||||
|
Тепер ви повинні побачити всі додані вами книги в
|
||||||
|
</string>
|
||||||
|
<string name="help_desc_how_to_add_books_4">Бібліотеці</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_customize_app">Як мені налаштувати застосунок?</string>
|
||||||
|
<string name="help_desc_how_to_customize_app_1">Перейдіть в</string>
|
||||||
|
<string name="help_desc_how_to_customize_app_2">Налаштування</string>
|
||||||
|
<string name="help_desc_how_to_customize_app_3">
|
||||||
|
Ви можете зробити це з екранів Бібліотека, Історія та Огляд
|
||||||
|
натиснувши на три крапки в правому верхньому кутку і вибравши
|
||||||
|
«Налаштування» з випадаючого списку.
|
||||||
|
Після цього ви побачите всі доступні налаштування для цього застосунку.
|
||||||
|
Не соромтеся експерементувати з ними!
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_move_or_delete_books">Як мені перемістити або видалити книги?</string>
|
||||||
|
<string name="help_desc_how_to_move_or_delete_books_1">Перейдіть в</string>
|
||||||
|
<string name="help_desc_how_to_move_or_delete_books_2">Бібліотеку</string>
|
||||||
|
<string name="help_desc_how_to_move_or_delete_books_3">
|
||||||
|
Утримуйте обкладинку першої книги, поки вона не буде вибрана,
|
||||||
|
тепер ви можете просто натиснути на всі обкладинки книг, які ви хочете вибрати.
|
||||||
|
Ви також можете вибрати книги в декількох категоріях.
|
||||||
|
Після того, як ви вибрали всі бажані книги, ви можете натиснути на іконку зі стрілкою,
|
||||||
|
щоб перемістити та на іконку кошика, щоб видалити книги.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_edit_book">Як мені відредагувати книгу?</string>
|
||||||
|
<string name="help_desc_how_to_edit_book_1">Перейдіть в</string>
|
||||||
|
<string name="help_desc_how_to_edit_book_2">Бібліотеку</string>
|
||||||
|
<string name="help_desc_how_to_edit_book_3">
|
||||||
|
та натісніть на обкладинку книги, щоб побачити подробиці книги.
|
||||||
|
Тепер ви можете утримувати назву книги, щоб відредагувати назву
|
||||||
|
або обкладинку, щоб змінити або видалити обкладинку.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_read_book">Як мені читати книгу?</string>
|
||||||
|
<string name="help_desc_how_to_read_book_1">Перейдіть в</string>
|
||||||
|
<string name="help_desc_how_to_read_book_2">Бібліотеку</string>
|
||||||
|
<string name="help_desc_how_to_read_book_3">
|
||||||
|
Щоб почати читати книгу, натисніть на кнопку в правому нижньому куті книги, яку ви хочете читати.
|
||||||
|
Також ви можете почати читати книгу, натиснувши на її обкладинку, щоб побачити подробиці книги,
|
||||||
|
а потім натиснути кнопку в правому нижньому куті.
|
||||||
|
Прогрес буде збережено і відновлено автоматично.
|
||||||
|
Ви можете показати верхню і нижню панелі, натиснувши в будь-якому місці екрану.
|
||||||
|
Натиснувши на назву книги, ви перейдете до подробиць книги.
|
||||||
|
Натиснувши на іконку з шестернею, ви побачите налаштування Читача.
|
||||||
|
Ви можете використовувати повзунок нижньої панелі для зміни прогресу.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_customize_reader">Як мені налаштувати Читач?</string>
|
||||||
|
<string name="help_desc_how_to_customize_reader_1">
|
||||||
|
Почніть читати книгу(див: Як мені читати книгу?),
|
||||||
|
після цього покажіть верхню і нижню панелі, натиснувши в будь-якому місці екрану.
|
||||||
|
Потім натисніть на іконку з шестернею в правому верхньому кутку.
|
||||||
|
Це покаже всі налаштування Читача.
|
||||||
|
Другий спосіб - перейти в
|
||||||
|
</string>
|
||||||
|
<string name="help_desc_how_to_customize_reader_2">
|
||||||
|
Налаштування
|
||||||
|
</string>
|
||||||
|
<string name="help_desc_how_to_customize_reader_3">
|
||||||
|
та вибрати категорію Читач, щоб налаштувати Головні параметри,
|
||||||
|
або категорію Зовнішній Вигляд, щоб налаштувати параметри Кольорів.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_update_book">Як мені оновити книгу?</string>
|
||||||
|
<string name="help_desc_how_to_update_book_1">Перейдіть в</string>
|
||||||
|
<string name="help_desc_how_to_update_book_2">Бібліотеку</string>
|
||||||
|
<string name="help_desc_how_to_update_book_3">
|
||||||
|
та натісніть на обкладинку книги, щоб побачити подробиці книги.
|
||||||
|
Потягніть екран вниз або натисніть на іконку оновлення у верхньому правому куті, щоб оновити книгу.
|
||||||
|
Якщо файл книги змінився, ви можете підтвердити і оновити книгу або скасувати оновлення.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Font families -->
|
<!-- Font families -->
|
||||||
<string name="default_font">За замовчуванням</string>
|
<string name="default_font">За замовчуванням</string>
|
||||||
|
|
@ -294,6 +401,11 @@
|
||||||
<string name="app_icon_content_desc">Іконка застосунку</string>
|
<string name="app_icon_content_desc">Іконка застосунку</string>
|
||||||
<string name="note_content_desc">Примітка</string>
|
<string name="note_content_desc">Примітка</string>
|
||||||
<string name="delete_history_element_content_desc">Видалити елемент історії</string>
|
<string name="delete_history_element_content_desc">Видалити елемент історії</string>
|
||||||
|
<string name="library_content_desc">Ваша бібліотека</string>
|
||||||
|
<string name="history_content_desc">Історія читання</string>
|
||||||
|
<string name="browse_content_desc">Додавайте книги</string>
|
||||||
|
<string name="arrow_content_desc">Стрілка</string>
|
||||||
|
<string name="arrow_anim_content_desc">Анімація стрілки</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="InconsistentArrays">
|
||||||
<!-- Basic Strings -->
|
<!-- Basic Strings -->
|
||||||
<string name="app_version" translatable="false">0.9.6</string>
|
<string name="app_version" translatable="false">0.9.7</string>
|
||||||
<string name="app_name">Book\'s Story</string>
|
<string name="app_name">Book\'s Story</string>
|
||||||
|
|
||||||
|
<!-- About sites -->
|
||||||
<string name="releases_page" translatable="false">
|
<string name="releases_page" translatable="false">
|
||||||
https://github.com/Acclorite/book-story/releases/latest
|
https://github.com/Acclorite/book-story/releases/latest
|
||||||
</string>
|
</string>
|
||||||
|
|
@ -18,6 +20,13 @@
|
||||||
https://www.buymeacoffee.com/acclorite
|
https://www.buymeacoffee.com/acclorite
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
|
<!-- Book sources -->
|
||||||
|
<string-array name="book_sites">
|
||||||
|
<item>Anna\'s Archive|https://annas-archive.org</item>
|
||||||
|
<item>Project Gutenberg|https://www.gutenberg.org</item>
|
||||||
|
<item>Standard Ebooks|https://standardebooks.org</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<!-- Screens -->
|
<!-- Screens -->
|
||||||
<string name="library_screen">Library</string>
|
<string name="library_screen">Library</string>
|
||||||
<string name="history_screen">History</string>
|
<string name="history_screen">History</string>
|
||||||
|
|
@ -123,7 +132,7 @@
|
||||||
<string name="continue_read">Continue</string>
|
<string name="continue_read">Continue</string>
|
||||||
<string name="never">Never</string>
|
<string name="never">Never</string>
|
||||||
<string name="go_back">Go back</string>
|
<string name="go_back">Go back</string>
|
||||||
<string name="move_to_read">Move this book to \"Already Read\"</string>
|
<string name="move_to_read">Move this book to «Already Read»</string>
|
||||||
<string name="back_to_library">Back to Library</string>
|
<string name="back_to_library">Back to Library</string>
|
||||||
<string name="undo">Undo</string>
|
<string name="undo">Undo</string>
|
||||||
<string name="copy">Copy</string>
|
<string name="copy">Copy</string>
|
||||||
|
|
@ -175,6 +184,7 @@
|
||||||
<!-- Settings options -->
|
<!-- Settings options -->
|
||||||
<string name="language_option">App language</string>
|
<string name="language_option">App language</string>
|
||||||
<string name="dark_theme_option">Dark theme</string>
|
<string name="dark_theme_option">Dark theme</string>
|
||||||
|
<string name="pure_dark_option">Pure dark (OLED)</string>
|
||||||
<string name="theme_contrast_option">Theme contrast</string>
|
<string name="theme_contrast_option">Theme contrast</string>
|
||||||
<string name="app_theme_option">App theme</string>
|
<string name="app_theme_option">App theme</string>
|
||||||
<string name="font_family_option">Font family</string>
|
<string name="font_family_option">Font family</string>
|
||||||
|
|
@ -196,6 +206,11 @@
|
||||||
<string name="dark_theme_on">Enabled</string>
|
<string name="dark_theme_on">Enabled</string>
|
||||||
<string name="dark_theme_follow_system">System</string>
|
<string name="dark_theme_follow_system">System</string>
|
||||||
|
|
||||||
|
<!-- Pure Dark properties -->
|
||||||
|
<string name="pure_dark_off">Disabled</string>
|
||||||
|
<string name="pure_dark_on">Enabled</string>
|
||||||
|
<string name="pure_dark_power_saver">Saver</string>
|
||||||
|
|
||||||
<!-- Theme Contrast properties -->
|
<!-- Theme Contrast properties -->
|
||||||
<string name="theme_contrast_standard">Standard</string>
|
<string name="theme_contrast_standard">Standard</string>
|
||||||
<string name="theme_contrast_medium">Medium</string>
|
<string name="theme_contrast_medium">Medium</string>
|
||||||
|
|
@ -272,10 +287,105 @@
|
||||||
<string name="slava_ukraini_option_desc">Heroyam Slava! 🐉</string>
|
<string name="slava_ukraini_option_desc">Heroyam Slava! 🐉</string>
|
||||||
|
|
||||||
<!-- Help -->
|
<!-- Help -->
|
||||||
<string name="clickable_note_1">Please note, that</string>
|
<string name="help_clickable_note_1">Please note, that</string>
|
||||||
<string name="clickable_note_2">such highlighted text</string>
|
<string name="help_clickable_note_2">such highlighted text</string>
|
||||||
<string name="clickable_note_3">can be clicked.</string>
|
<string name="help_clickable_note_3">
|
||||||
<string name="clickable_note_action">I\'m clickable!</string>
|
can be clicked.
|
||||||
|
Also you can hide this note by clicking the icon, try it out!
|
||||||
|
</string>
|
||||||
|
<string name="help_clickable_note_action">I\'m clickable!</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_find_books">How do I find and download books?</string>
|
||||||
|
<string name="help_desc_how_to_find_books">
|
||||||
|
You can try to find desired books using sites from provided note
|
||||||
|
or you can use text field to search for books online.
|
||||||
|
</string>
|
||||||
|
<string name="help_field_placeholder">Book\'s name</string>
|
||||||
|
<string name="help_field_support">Useful sites with books:</string>
|
||||||
|
<string name="help_field_error">Book\'s name should not be empty</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_add_books">How do I add books to the Library?</string>
|
||||||
|
<string name="help_desc_how_to_add_books_1">Go to the</string>
|
||||||
|
<string name="help_desc_how_to_add_books_2">Browse</string>
|
||||||
|
<string name="help_desc_how_to_add_books_3">
|
||||||
|
If you don\'t see downloaded books try to refresh list by pulling it down.
|
||||||
|
Make sure your books has supported file format.
|
||||||
|
Then click the book to select it or hold to show it\'s location.
|
||||||
|
After you selected all books you want, click the checkmark icon in the upper right corner,
|
||||||
|
wait for all books to load and click «Add».
|
||||||
|
Now you should see all books you have added in the
|
||||||
|
</string>
|
||||||
|
<string name="help_desc_how_to_add_books_4">Library</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_customize_app">How do I customize the app?</string>
|
||||||
|
<string name="help_desc_how_to_customize_app_1">Go to the</string>
|
||||||
|
<string name="help_desc_how_to_customize_app_2">Settings</string>
|
||||||
|
<string name="help_desc_how_to_customize_app_3">
|
||||||
|
You can do this from the Library, History and Browse screens
|
||||||
|
by clicking three dots in the upper right corner
|
||||||
|
and selecting «Settings» from the drop-down menu.
|
||||||
|
Then you will see all available settings for this app.
|
||||||
|
Feel free to experiment with those!
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_move_or_delete_books">How do I move or delete books?</string>
|
||||||
|
<string name="help_desc_how_to_move_or_delete_books_1">Go to the</string>
|
||||||
|
<string name="help_desc_how_to_move_or_delete_books_2">Library</string>
|
||||||
|
<string name="help_desc_how_to_move_or_delete_books_3">
|
||||||
|
Hold down the first book\'s cover image until it is selected,
|
||||||
|
now you can simply click all cover images of books you want to select.
|
||||||
|
You can also select books in multiple categories.
|
||||||
|
After you have chosen all desired books, you can click the arrow icon to move
|
||||||
|
and trash bin icon to delete books.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_edit_book">How do I edit a book?</string>
|
||||||
|
<string name="help_desc_how_to_edit_book_1">Go to the</string>
|
||||||
|
<string name="help_desc_how_to_edit_book_2">Library</string>
|
||||||
|
<string name="help_desc_how_to_edit_book_3">
|
||||||
|
and click the book\'s cover image to see book\'s details.
|
||||||
|
Now you can hold down the book\'s title to edit title
|
||||||
|
or cover image to change or delete cover image.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_read_book">How do I read a book?</string>
|
||||||
|
<string name="help_desc_how_to_read_book_1">Go to the</string>
|
||||||
|
<string name="help_desc_how_to_read_book_2">Library</string>
|
||||||
|
<string name="help_desc_how_to_read_book_3">
|
||||||
|
To start reading the book click the button in the bottom right corner of the book you want to read.
|
||||||
|
Also you can start reading the book by clicking it\'s cover image
|
||||||
|
to see book\'s details and then click the button in the bottom right corner.
|
||||||
|
The progress will be saved and restored automatically.
|
||||||
|
You can bring top and bottom bars by clicking anywhere on the screen.
|
||||||
|
By clicking the title of the book you will be transferred to the book\'s details.
|
||||||
|
By clicking the gear icon you will see Reader settings.
|
||||||
|
You can use bottom bar\'s slider to change progress.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_customize_reader">How do I customize the Reader?</string>
|
||||||
|
<string name="help_desc_how_to_customize_reader_1">
|
||||||
|
Start reading the book(see: How do I read a book?),
|
||||||
|
after that bring top and bottom bars by clicking anywhere on the screen.
|
||||||
|
Then click the gear icon in the upper right corner.
|
||||||
|
This will show all Reader settings.
|
||||||
|
Second way is to go to the
|
||||||
|
</string>
|
||||||
|
<string name="help_desc_how_to_customize_reader_2">
|
||||||
|
Settings
|
||||||
|
</string>
|
||||||
|
<string name="help_desc_how_to_customize_reader_3">
|
||||||
|
and select Reader category to customize General settings,
|
||||||
|
or Appearance category to customize Colors settings.
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="help_title_how_to_update_book">How do I update a book?</string>
|
||||||
|
<string name="help_desc_how_to_update_book_1">Go to the</string>
|
||||||
|
<string name="help_desc_how_to_update_book_2">Library</string>
|
||||||
|
<string name="help_desc_how_to_update_book_3">
|
||||||
|
and click the book\'s cover image to see book\'s details.
|
||||||
|
Pull down the screen or click the refresh icon in the upper right corner to update the book.
|
||||||
|
If book\'s file changed you may confirm and update the book or cancel update.
|
||||||
|
</string>
|
||||||
|
|
||||||
<!-- Font families -->
|
<!-- Font families -->
|
||||||
<string name="default_font">Default</string>
|
<string name="default_font">Default</string>
|
||||||
|
|
@ -309,5 +419,10 @@
|
||||||
<string name="app_icon_content_desc">App icon</string>
|
<string name="app_icon_content_desc">App icon</string>
|
||||||
<string name="note_content_desc">Note</string>
|
<string name="note_content_desc">Note</string>
|
||||||
<string name="delete_history_element_content_desc">Delete history element</string>
|
<string name="delete_history_element_content_desc">Delete history element</string>
|
||||||
|
<string name="library_content_desc">Your library</string>
|
||||||
|
<string name="history_content_desc">Reading history</string>
|
||||||
|
<string name="browse_content_desc">Add books</string>
|
||||||
|
<string name="arrow_content_desc">Arrow</string>
|
||||||
|
<string name="arrow_anim_content_desc">Arrow animation</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<style name="Theme.BooksHistoryResurrection" parent="Theme.AppCompat.NoActionBar">
|
<style name="Theme.BookStory" parent="Theme.AppCompat.NoActionBar">
|
||||||
<item name="android:forceDarkAllowed" tools:ignore="NewApi">false</item>
|
<item name="android:forceDarkAllowed" tools:ignore="NewApi">false</item>
|
||||||
<item name="android:windowContentTransitions">false</item>
|
<item name="android:windowContentTransitions">false</item>
|
||||||
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
<style name="Theme.Start.Splash" parent="Theme.SplashScreen">
|
<style name="Theme.Start.Splash" parent="Theme.SplashScreen">
|
||||||
<item name="android:forceDarkAllowed" tools:ignore="NewApi">false</item>
|
<item name="android:forceDarkAllowed" tools:ignore="NewApi">false</item>
|
||||||
<item name="postSplashScreenTheme">@style/Theme.BooksHistoryResurrection</item>
|
<item name="postSplashScreenTheme">@style/Theme.BookStory</item>
|
||||||
|
|
||||||
<item name="windowSplashScreenBackground">@color/splash_bg_light</item>
|
<item name="windowSplashScreenBackground">@color/splash_bg_light</item>
|
||||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue