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
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "0.9.6"
|
||||
versionName = "0.9.7"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
|
@ -66,7 +66,7 @@ dependencies {
|
|||
implementation("androidx.activity:activity-compose:1.8.2")
|
||||
|
||||
// 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.animation:animation")
|
||||
implementation("androidx.compose.animation:animation-core")
|
||||
|
|
@ -90,7 +90,7 @@ dependencies {
|
|||
testImplementation("androidx.test.ext:truth:1.5.0")
|
||||
testImplementation("com.squareup.okhttp3:mockwebserver: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
|
||||
androidTestImplementation("com.google.dagger:hilt-android-testing:latest.release")
|
||||
|
|
@ -109,7 +109,7 @@ dependencies {
|
|||
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.2-alpha")
|
||||
|
||||
// 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")
|
||||
implementation("com.google.dagger:hilt-compiler:latest.release")
|
||||
ksp("androidx.hilt:hilt-compiler:latest.release")
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Start.Splash"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
tools:targetApi="tiramisu"
|
||||
tools:ignore="DataExtractionRules">
|
||||
|
|
|
|||
|
|
@ -9,19 +9,25 @@ import androidx.activity.viewModels
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
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.unit.LayoutDirection
|
||||
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.general.GeneralSettings
|
||||
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.isDark
|
||||
import ua.acclorite.book_story.presentation.ui.isPureDark
|
||||
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) {
|
||||
BooksHistoryResurrectionTheme(
|
||||
BookStoryTheme(
|
||||
theme = state.theme!!,
|
||||
isDark = state.darkTheme!!.isDark(),
|
||||
isPureDark = state.pureDark!!.isPureDark(this),
|
||||
themeContrast = state.themeContrast!!
|
||||
) {
|
||||
NavigationHost(startScreen = Screen.LIBRARY) {
|
||||
|
|
|
|||
|
|
@ -5,5 +5,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
|
||||
interface DataStore {
|
||||
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)
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import androidx.datastore.preferences.core.emptyPreferences
|
|||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
|
@ -29,6 +30,26 @@ class DataStoreImpl @Inject constructor(context: Application) : DataStore {
|
|||
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) {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[key] = value
|
||||
|
|
|
|||
|
|
@ -6,9 +6,14 @@ import android.net.Uri
|
|||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
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.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import ua.acclorite.book_story.R
|
||||
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.Resource
|
||||
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.FileOutputStream
|
||||
import java.util.UUID
|
||||
|
|
@ -380,6 +385,32 @@ class BookRepositoryImpl @Inject constructor(
|
|||
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>>> {
|
||||
fun getAllFilesInDirectory(directory: File): List<File> {
|
||||
val filesList = mutableListOf<File>()
|
||||
|
|
@ -437,7 +468,7 @@ class BookRepositoryImpl @Inject constructor(
|
|||
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())
|
||||
|
||||
if (!isQuery) {
|
||||
|
|
@ -451,18 +482,9 @@ class BookRepositoryImpl @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
filteredFiles = if (query.trim().isNotEmpty()) {
|
||||
filteredFiles.sortedByDescending {
|
||||
calculateFamiliarity(
|
||||
query,
|
||||
it.name.lowercase().trim()
|
||||
)
|
||||
}.toMutableList()
|
||||
} else {
|
||||
filteredFiles.sortedByDescending {
|
||||
filteredFiles = filteredFiles.sortedByDescending {
|
||||
it.lastModified()
|
||||
}.toMutableList()
|
||||
}
|
||||
|
||||
emit(
|
||||
Resource.Success(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package ua.acclorite.book_story.domain.repository
|
||||
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import ua.acclorite.book_story.domain.model.Book
|
||||
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.util.CoverImage
|
||||
import ua.acclorite.book_story.domain.util.Resource
|
||||
import ua.acclorite.book_story.presentation.data.MainState
|
||||
import java.io.File
|
||||
|
||||
interface BookRepository {
|
||||
|
|
@ -55,6 +57,8 @@ interface BookRepository {
|
|||
value: T
|
||||
)
|
||||
|
||||
suspend fun getAllSettings(scope: CoroutineScope): MainState
|
||||
|
||||
suspend fun getFilesFromDevice(query: String = ""): Flow<Resource<List<File>>>
|
||||
|
||||
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 THEME = stringPreferencesKey("theme")
|
||||
val DARK_THEME = stringPreferencesKey("dark_theme")
|
||||
val PURE_DARK = stringPreferencesKey("pure_dark")
|
||||
val THEME_CONTRAST = stringPreferencesKey("theme_contrast")
|
||||
val SHOW_START_SCREEN = booleanPreferencesKey("guide")
|
||||
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.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -31,6 +32,9 @@ fun CustomIconButton(
|
|||
CustomTooltip(text = stringResource(id = contentDescription)) {
|
||||
IconButton(
|
||||
enabled = enabled && !isClicked,
|
||||
modifier = Modifier.focusProperties {
|
||||
canFocus = false
|
||||
},
|
||||
onClick = {
|
||||
if (disableOnClick) {
|
||||
isClicked = true
|
||||
|
|
|
|||
|
|
@ -7,13 +7,15 @@ import androidx.compose.material3.TooltipBox
|
|||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CustomTooltip(text: String, content: @Composable () -> Unit) {
|
||||
fun CustomTooltip(text: String, padding: Dp = 14.dp, content: @Composable () -> Unit) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(12.dp),
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(padding),
|
||||
focusable = false,
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(text = text)
|
||||
|
|
|
|||
|
|
@ -34,32 +34,38 @@ fun BottomNavigationBar(
|
|||
}
|
||||
|
||||
NavigationBar {
|
||||
|
||||
BottomNavigationBarItem(
|
||||
item = NavigationItem(
|
||||
stringResource(id = R.string.library_screen),
|
||||
selectedIcon = painterResource(id = R.drawable.library_screen_filled),
|
||||
unselectedIcon = painterResource(id = R.drawable.library_screen_outlined)
|
||||
),
|
||||
tooltipText = stringResource(id = R.string.library_content_desc),
|
||||
isSelected = currentScreen == Screen.LIBRARY
|
||||
) {
|
||||
navigator.navigate(Screen.LIBRARY, false)
|
||||
}
|
||||
|
||||
BottomNavigationBarItem(
|
||||
item = NavigationItem(
|
||||
stringResource(id = R.string.history_screen),
|
||||
selectedIcon = painterResource(id = R.drawable.history_screen_filled),
|
||||
unselectedIcon = painterResource(id = R.drawable.history_screen_outlined)
|
||||
),
|
||||
tooltipText = stringResource(id = R.string.history_content_desc),
|
||||
isSelected = currentScreen == Screen.HISTORY
|
||||
) {
|
||||
navigator.navigate(Screen.HISTORY, false)
|
||||
}
|
||||
|
||||
BottomNavigationBarItem(
|
||||
item = NavigationItem(
|
||||
stringResource(id = R.string.browse_screen),
|
||||
selectedIcon = painterResource(id = R.drawable.browse_screen_filled),
|
||||
unselectedIcon = painterResource(id = R.drawable.browse_screen_outlined)
|
||||
),
|
||||
tooltipText = stringResource(id = R.string.browse_content_desc),
|
||||
isSelected = currentScreen == Screen.BROWSE
|
||||
) {
|
||||
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.unit.dp
|
||||
import ua.acclorite.book_story.domain.model.NavigationItem
|
||||
import ua.acclorite.book_story.presentation.components.CustomTooltip
|
||||
|
||||
/**
|
||||
* Bottom Navigation Bar Item, uses default [NavigationBarItem].
|
||||
|
|
@ -19,6 +20,7 @@ import ua.acclorite.book_story.domain.model.NavigationItem
|
|||
fun RowScope.BottomNavigationBarItem(
|
||||
modifier: Modifier = Modifier,
|
||||
item: NavigationItem,
|
||||
tooltipText: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
|
|
@ -27,22 +29,33 @@ fun RowScope.BottomNavigationBarItem(
|
|||
else item.unselectedIcon
|
||||
}
|
||||
|
||||
|
||||
NavigationBarItem(
|
||||
label = {
|
||||
CustomTooltip(
|
||||
text = tooltipText,
|
||||
padding = 64.dp
|
||||
) {
|
||||
Text(
|
||||
text = item.title,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
},
|
||||
selected = isSelected,
|
||||
onClick = { onClick() },
|
||||
icon = {
|
||||
CustomTooltip(
|
||||
text = tooltipText,
|
||||
padding = 32.dp
|
||||
) {
|
||||
Icon(
|
||||
painter = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = modifier
|
||||
)
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ fun BoxScope.CustomNavigationRail(
|
|||
selectedIcon = painterResource(id = R.drawable.library_screen_filled),
|
||||
unselectedIcon = painterResource(id = R.drawable.library_screen_outlined)
|
||||
),
|
||||
tooltipText = stringResource(id = R.string.library_content_desc),
|
||||
isSelected = currentScreen == Screen.LIBRARY
|
||||
) {
|
||||
navigator.navigate(Screen.LIBRARY, false)
|
||||
|
|
@ -86,6 +87,7 @@ fun BoxScope.CustomNavigationRail(
|
|||
selectedIcon = painterResource(id = R.drawable.history_screen_filled),
|
||||
unselectedIcon = painterResource(id = R.drawable.history_screen_outlined)
|
||||
),
|
||||
tooltipText = stringResource(id = R.string.history_content_desc),
|
||||
isSelected = currentScreen == Screen.HISTORY
|
||||
) {
|
||||
navigator.navigate(Screen.HISTORY, false)
|
||||
|
|
@ -96,6 +98,7 @@ fun BoxScope.CustomNavigationRail(
|
|||
selectedIcon = painterResource(id = R.drawable.browse_screen_filled),
|
||||
unselectedIcon = painterResource(id = R.drawable.browse_screen_outlined)
|
||||
),
|
||||
tooltipText = stringResource(id = R.string.browse_content_desc),
|
||||
isSelected = currentScreen == Screen.BROWSE
|
||||
) {
|
||||
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.unit.dp
|
||||
import ua.acclorite.book_story.domain.model.NavigationItem
|
||||
import ua.acclorite.book_story.presentation.components.CustomTooltip
|
||||
|
||||
/**
|
||||
* Custom Navigation Rail item.
|
||||
|
|
@ -18,6 +19,7 @@ import ua.acclorite.book_story.domain.model.NavigationItem
|
|||
fun CustomNavigationRailItem(
|
||||
modifier: Modifier = Modifier,
|
||||
item: NavigationItem,
|
||||
tooltipText: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
|
|
@ -28,20 +30,30 @@ fun CustomNavigationRailItem(
|
|||
|
||||
NavigationRailItem(
|
||||
label = {
|
||||
CustomTooltip(
|
||||
text = tooltipText,
|
||||
padding = 48.dp
|
||||
) {
|
||||
Text(
|
||||
text = item.title,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
},
|
||||
selected = isSelected,
|
||||
onClick = { onClick() },
|
||||
icon = {
|
||||
CustomTooltip(
|
||||
text = tooltipText,
|
||||
padding = 16.dp
|
||||
) {
|
||||
Icon(
|
||||
painter = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = modifier
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,9 +10,3 @@ fun String.removeTrailingZero(): String {
|
|||
|
||||
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 OnChangeTheme(val theme: 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 OnChangeFontFamily(val fontFamily: String) : MainEvent()
|
||||
data class OnChangeFontStyle(val fontStyle: Boolean) : MainEvent()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
package ua.acclorite.book_story.presentation.data
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
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.PureDark
|
||||
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.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.
|
||||
|
|
@ -15,6 +25,7 @@ data class MainState(
|
|||
val language: String? = null,
|
||||
val theme: Theme? = null,
|
||||
val darkTheme: DarkTheme? = null,
|
||||
val pureDark: PureDark? = null,
|
||||
val themeContrast: ThemeContrast? = null,
|
||||
val fontFamily: String? = null,
|
||||
val isItalic: Boolean? = null,
|
||||
|
|
@ -34,6 +45,8 @@ data class MainState(
|
|||
// String
|
||||
darkTheme = DarkTheme.valueOf(parcel.readString() ?: DarkTheme.FOLLOW_SYSTEM.name),
|
||||
// String
|
||||
pureDark = PureDark.valueOf(parcel.readString() ?: PureDark.OFF.name),
|
||||
// String
|
||||
themeContrast = ThemeContrast.valueOf(parcel.readString() ?: ThemeContrast.STANDARD.name),
|
||||
// String
|
||||
fontFamily = parcel.readString(),
|
||||
|
|
@ -64,6 +77,8 @@ data class MainState(
|
|||
parcel.writeString(theme?.name)
|
||||
// Dark Theme
|
||||
parcel.writeString(darkTheme?.name)
|
||||
// Pure Dark
|
||||
parcel.writeString(pureDark?.name)
|
||||
// Contrast Level
|
||||
parcel.writeString(themeContrast?.name)
|
||||
// Font Family
|
||||
|
|
@ -106,5 +121,79 @@ data class MainState(
|
|||
override fun newArray(size: Int): Array<MainState?> {
|
||||
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
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
|
@ -9,23 +7,21 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
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.util.Constants
|
||||
import ua.acclorite.book_story.domain.util.DataStoreConstants
|
||||
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.toPureDark
|
||||
import ua.acclorite.book_story.presentation.ui.toTheme
|
||||
import ua.acclorite.book_story.presentation.ui.toThemeContrast
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
|
@ -35,14 +31,17 @@ import javax.inject.Inject
|
|||
class MainViewModel @Inject constructor(
|
||||
private val stateHandle: SavedStateHandle,
|
||||
|
||||
private val getDatastore: GetDatastore,
|
||||
private val setDatastore: SetDatastore,
|
||||
private val changeLanguage: ChangeLanguage,
|
||||
private val getAllSettings: GetAllSettings
|
||||
) : ViewModel() {
|
||||
|
||||
private val _isReady = MutableStateFlow(false)
|
||||
val isReady = _isReady.asStateFlow()
|
||||
|
||||
private val isSettingsReady = MutableStateFlow(false)
|
||||
private val isViewModelReady = MutableStateFlow(false)
|
||||
|
||||
private val _state: MutableStateFlow<MainState> = MutableStateFlow(
|
||||
stateHandle[Constants.MAIN_STATE] ?: MainState()
|
||||
)
|
||||
|
|
@ -53,7 +52,7 @@ class MainViewModel @Inject constructor(
|
|||
is MainEvent.OnChangeLanguage -> {
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
changeLanguage.execute(event.lang)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
language = event.lang
|
||||
)
|
||||
|
|
@ -64,7 +63,7 @@ class MainViewModel @Inject constructor(
|
|||
is MainEvent.OnChangeDarkTheme -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
setDatastore.execute(DataStoreConstants.DARK_THEME, event.darkTheme)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
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 -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
setDatastore.execute(DataStoreConstants.THEME_CONTRAST, event.themeContrast)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
themeContrast = event.themeContrast.toThemeContrast()
|
||||
)
|
||||
|
|
@ -86,7 +96,7 @@ class MainViewModel @Inject constructor(
|
|||
is MainEvent.OnChangeTheme -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
setDatastore.execute(DataStoreConstants.THEME, event.theme)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
theme = event.theme.toTheme()
|
||||
)
|
||||
|
|
@ -97,7 +107,7 @@ class MainViewModel @Inject constructor(
|
|||
is MainEvent.OnChangeFontFamily -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
setDatastore.execute(DataStoreConstants.FONT, event.fontFamily)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
fontFamily = Constants.FONTS.find { font -> font.id == event.fontFamily }?.id
|
||||
?: Constants.FONTS[0].id
|
||||
|
|
@ -109,7 +119,7 @@ class MainViewModel @Inject constructor(
|
|||
is MainEvent.OnChangeFontStyle -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
setDatastore.execute(DataStoreConstants.IS_ITALIC, event.fontStyle)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
isItalic = event.fontStyle
|
||||
)
|
||||
|
|
@ -120,7 +130,7 @@ class MainViewModel @Inject constructor(
|
|||
is MainEvent.OnChangeFontSize -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
setDatastore.execute(DataStoreConstants.FONT_SIZE, event.fontSize)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
fontSize = event.fontSize
|
||||
)
|
||||
|
|
@ -131,7 +141,7 @@ class MainViewModel @Inject constructor(
|
|||
is MainEvent.OnChangeLineHeight -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
setDatastore.execute(DataStoreConstants.LINE_HEIGHT, event.lineHeight)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
lineHeight = event.lineHeight
|
||||
)
|
||||
|
|
@ -142,7 +152,7 @@ class MainViewModel @Inject constructor(
|
|||
is MainEvent.OnChangeParagraphHeight -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
setDatastore.execute(DataStoreConstants.PARAGRAPH_HEIGHT, event.paragraphHeight)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
paragraphHeight = event.paragraphHeight
|
||||
)
|
||||
|
|
@ -153,7 +163,7 @@ class MainViewModel @Inject constructor(
|
|||
is MainEvent.OnChangeParagraphIndentation -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
setDatastore.execute(DataStoreConstants.PARAGRAPH_INDENTATION, event.bool)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
paragraphIndentation = event.bool
|
||||
)
|
||||
|
|
@ -164,7 +174,7 @@ class MainViewModel @Inject constructor(
|
|||
is MainEvent.OnChangeBackgroundColor -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
setDatastore.execute(DataStoreConstants.BACKGROUND_COLOR, event.color)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
backgroundColor = event.color
|
||||
)
|
||||
|
|
@ -175,7 +185,7 @@ class MainViewModel @Inject constructor(
|
|||
is MainEvent.OnChangeFontColor -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
setDatastore.execute(DataStoreConstants.FONT_COLOR, event.color)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
fontColor = event.color
|
||||
)
|
||||
|
|
@ -186,7 +196,7 @@ class MainViewModel @Inject constructor(
|
|||
is MainEvent.OnChangeShowStartScreen -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
setDatastore.execute(DataStoreConstants.SHOW_START_SCREEN, event.bool)
|
||||
_state.updateWithSavedHandle {
|
||||
updateStateWithSavedHandle {
|
||||
it.copy(
|
||||
showStartScreen = event.bool
|
||||
)
|
||||
|
|
@ -199,204 +209,54 @@ class MainViewModel @Inject constructor(
|
|||
fun init(
|
||||
libraryViewModel: LibraryViewModel,
|
||||
) {
|
||||
val isViewModelsReady = combine(
|
||||
libraryViewModel.isReady,
|
||||
) { values ->
|
||||
values.all { it }
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
val settings = getAllSettings.execute(viewModelScope)
|
||||
|
||||
// All additional execution
|
||||
changeLanguage.execute(settings.language!!)
|
||||
|
||||
updateStateWithSavedHandle {
|
||||
settings
|
||||
}
|
||||
isSettingsReady.update { true }
|
||||
}
|
||||
|
||||
val isDataReady = combine(
|
||||
_state
|
||||
) {
|
||||
val value = it.first()
|
||||
|
||||
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
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
libraryViewModel.isReady.collectLatest { ready ->
|
||||
isViewModelReady.update {
|
||||
ready
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val isReady = combine(
|
||||
isViewModelsReady,
|
||||
isDataReady
|
||||
isViewModelReady,
|
||||
isSettingsReady
|
||||
) { values ->
|
||||
values.all { it }
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
isReady.collect { bool ->
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
isReady.first { bool ->
|
||||
if (bool) {
|
||||
_isReady.update {
|
||||
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(
|
||||
const: String = Constants.MAIN_STATE,
|
||||
function: (T) -> T
|
||||
private fun updateStateWithSavedHandle(
|
||||
function: (MainState) -> MainState
|
||||
) {
|
||||
val nextValue = function(value)
|
||||
update {
|
||||
stateHandle[const] = nextValue
|
||||
nextValue
|
||||
_state.update {
|
||||
stateHandle[Constants.MAIN_STATE] = function(it)
|
||||
function(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -32,6 +31,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import ua.acclorite.book_story.R
|
||||
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.screens.about.components.AboutItem
|
||||
import ua.acclorite.book_story.presentation.screens.about.data.AboutEvent
|
||||
|
|
@ -44,17 +44,12 @@ fun AboutScreen(
|
|||
navigator: Navigator
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val listState = rememberLazyListState()
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
canScroll = {
|
||||
listState.canScrollForward || listState.canScrollBackward
|
||||
}
|
||||
)
|
||||
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
||||
|
||||
Scaffold(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
topBar = {
|
||||
|
|
@ -65,7 +60,7 @@ fun AboutScreen(
|
|||
navigationIcon = {
|
||||
GoBackButton(navigator = navigator)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
scrollBehavior = scrollState.first,
|
||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
|
|
@ -77,7 +72,7 @@ fun AboutScreen(
|
|||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding()),
|
||||
state = listState
|
||||
state = scrollState.second
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ fun BookInfoScreen(
|
|||
val listState = rememberLazyListState()
|
||||
val snackbarState = remember { SnackbarHostState() }
|
||||
val refreshState = rememberPullRefreshState(
|
||||
refreshing = state.isRefreshing,
|
||||
refreshing = state.isRefreshing || state.isLoadingUpdate,
|
||||
onRefresh = {
|
||||
viewModel.onEvent(
|
||||
BookInfoEvent.OnLoadUpdate(
|
||||
|
|
@ -161,7 +161,9 @@ fun BookInfoScreen(
|
|||
isTopBarScrolled = null,
|
||||
|
||||
content1NavigationIcon = {
|
||||
GoBackButton(navigator = navigator, enabled = !state.isRefreshing)
|
||||
GoBackButton(navigator = navigator, enabled = !state.isRefreshing) {
|
||||
viewModel.onEvent(BookInfoEvent.OnCancelUpdate)
|
||||
}
|
||||
},
|
||||
content1Title = {
|
||||
DefaultTransition(visible = firstVisibleItemIndex > 0) {
|
||||
|
|
@ -179,7 +181,7 @@ fun BookInfoScreen(
|
|||
icon = Icons.Default.Refresh,
|
||||
contentDescription = R.string.refresh_book_content_desc,
|
||||
disableOnClick = false,
|
||||
enabled = !state.isRefreshing
|
||||
enabled = !state.isRefreshing && !state.isLoadingUpdate
|
||||
) {
|
||||
viewModel.onEvent(
|
||||
BookInfoEvent.OnLoadUpdate(
|
||||
|
|
@ -319,7 +321,7 @@ fun BookInfoScreen(
|
|||
)
|
||||
|
||||
PullRefreshIndicator(
|
||||
state.isRefreshing,
|
||||
state.isRefreshing || state.isLoadingUpdate,
|
||||
refreshState,
|
||||
Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
|
|
@ -332,6 +334,7 @@ fun BookInfoScreen(
|
|||
|
||||
BackHandler {
|
||||
if (!state.isRefreshing) {
|
||||
viewModel.onEvent(BookInfoEvent.OnCancelUpdate)
|
||||
navigator.navigateBack()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
|
|||
fontFamily = MaterialTheme.typography.headlineSmall.fontFamily
|
||||
),
|
||||
onValueChange = {
|
||||
if (it.length < 80) {
|
||||
if (it.length < 100 || it.length < state.titleValue.length) {
|
||||
viewModel.onEvent(BookInfoEvent.OnTitleValueChange(it))
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ sealed class BookInfoEvent {
|
|||
val textUpdated: Boolean,
|
||||
) : BookInfoEvent()
|
||||
|
||||
data object OnCancelUpdate : BookInfoEvent()
|
||||
|
||||
data class OnConfirmUpdate(
|
||||
val snackbarState: SnackbarHostState,
|
||||
val context: Context,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import ua.acclorite.book_story.domain.util.Constants
|
|||
data class BookInfoState(
|
||||
val book: Book = Constants.EMPTY_BOOK,
|
||||
|
||||
val isLoadingUpdate: Boolean = false,
|
||||
val isRefreshing: Boolean = false,
|
||||
val showConfirmUpdateDialog: Boolean = false,
|
||||
val updatedBook: Book? = null,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
val state = _state.asStateFlow()
|
||||
|
||||
private var job: Job? = null
|
||||
private var job2: Job? = null
|
||||
|
||||
fun onEvent(event: BookInfoEvent) {
|
||||
when (event) {
|
||||
|
|
@ -290,14 +291,17 @@ class BookInfoViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
is BookInfoEvent.OnLoadUpdate -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
onEvent(BookInfoEvent.OnCancelUpdate)
|
||||
|
||||
job2 = viewModelScope.launch(Dispatchers.IO) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = true,
|
||||
isLoadingUpdate = true,
|
||||
editTitle = false
|
||||
)
|
||||
}
|
||||
|
||||
yield()
|
||||
if (_state.value.book.file == null) {
|
||||
onEvent(
|
||||
BookInfoEvent.OnShowSnackbar(
|
||||
|
|
@ -323,13 +327,15 @@ class BookInfoViewModel @Inject constructor(
|
|||
delay(500)
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = false
|
||||
isLoadingUpdate = false
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
yield()
|
||||
val nullableBook = getBookFromFile.execute(_state.value.book.file!!)
|
||||
yield()
|
||||
if (nullableBook is NullableBook.Null) {
|
||||
onEvent(
|
||||
BookInfoEvent.OnShowSnackbar(
|
||||
|
|
@ -352,11 +358,12 @@ class BookInfoViewModel @Inject constructor(
|
|||
delay(500)
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = false
|
||||
isLoadingUpdate = false
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
yield()
|
||||
|
||||
val updatedBook = nullableBook.book?.first ?: return@launch
|
||||
val book = _state.value.book
|
||||
|
|
@ -383,6 +390,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
textUpdated = true
|
||||
}
|
||||
|
||||
yield()
|
||||
if (!authorUpdated && !descriptionUpdated && !textUpdated) {
|
||||
onEvent(
|
||||
BookInfoEvent.OnShowSnackbar(
|
||||
|
|
@ -395,12 +403,13 @@ class BookInfoViewModel @Inject constructor(
|
|||
delay(500)
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = false
|
||||
isLoadingUpdate = false
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
yield()
|
||||
onEvent(
|
||||
BookInfoEvent.OnShowConfirmUpdateDialog(
|
||||
updatedBook = updatedBook,
|
||||
|
|
@ -412,7 +421,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
|
||||
_state.update {
|
||||
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 -> {
|
||||
viewModelScope.launch {
|
||||
onEvent(BookInfoEvent.OnCancelUpdate)
|
||||
_state.value.book.id.let {
|
||||
insertHistory.execute(
|
||||
listOf(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import androidx.activity.compose.BackHandler
|
|||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
|
|
@ -71,7 +70,6 @@ import ua.acclorite.book_story.presentation.ui.Transitions
|
|||
@OptIn(
|
||||
ExperimentalMaterial3Api::class,
|
||||
ExperimentalPermissionsApi::class,
|
||||
ExperimentalFoundationApi::class,
|
||||
ExperimentalMaterialApi::class
|
||||
)
|
||||
@Composable
|
||||
|
|
@ -249,7 +247,7 @@ fun BrowseScreen(
|
|||
) { selectableFile ->
|
||||
BrowseFileItem(
|
||||
file = selectableFile,
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
hasSelectedFiles = state.selectableFiles.any { it.second },
|
||||
onLongClick = {
|
||||
Toast.makeText(
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import androidx.compose.foundation.layout.Spacer
|
|||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -18,34 +18,38 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import ua.acclorite.book_story.R
|
||||
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.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
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@OptIn(
|
||||
ExperimentalMaterial3Api::class
|
||||
)
|
||||
@Composable
|
||||
fun HelpScreen(
|
||||
viewModel: HelpViewModel = hiltViewModel(),
|
||||
navigator: Navigator
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val listState = rememberLazyListState()
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
canScroll = {
|
||||
listState.canScrollForward || listState.canScrollBackward
|
||||
}
|
||||
)
|
||||
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
||||
|
||||
Scaffold(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
topBar = {
|
||||
|
|
@ -56,7 +60,7 @@ fun HelpScreen(
|
|||
navigationIcon = {
|
||||
GoBackButton(navigator = navigator)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
scrollBehavior = scrollState.first,
|
||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
|
|
@ -67,19 +71,88 @@ fun HelpScreen(
|
|||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding())
|
||||
.padding(horizontal = 18.dp),
|
||||
state = listState
|
||||
.padding(top = paddingValues.calculateTopPadding(), start = 18.dp, end = 18.dp)
|
||||
.imePadding(),
|
||||
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
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.height
|
||||
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.outlined.Info
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
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 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
|
||||
fun HelpClickableNote() {
|
||||
val context = LocalContext.current
|
||||
|
||||
Column {
|
||||
fun LazyItemScope.HelpClickableNote(
|
||||
text: AnnotatedString,
|
||||
tags: List<String> = emptyList(),
|
||||
isNoteFullyShown: Boolean,
|
||||
onIconClick: () -> Unit,
|
||||
onTagClick: (String) -> Unit
|
||||
) {
|
||||
Column(Modifier.animateItem()) {
|
||||
CustomTooltip(text = stringResource(R.string.note_content_desc)) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Info,
|
||||
contentDescription = stringResource(id = R.string.note_content_desc),
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
SlidingTransition(visible = isNoteFullyShown) {
|
||||
Column {
|
||||
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)
|
||||
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
|
||||
|
||||
sealed class HelpEvent {
|
||||
data class OnSearchInWeb(
|
||||
val context: Context,
|
||||
val noAppsFound: () -> Unit
|
||||
) : HelpEvent()
|
||||
|
||||
data class OnNavigateToBrowserPage(
|
||||
val page: String,
|
||||
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
|
||||
|
||||
import android.app.SearchManager
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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 javax.inject.Inject
|
||||
|
||||
|
|
@ -13,6 +17,9 @@ class HelpViewModel @Inject constructor(
|
|||
|
||||
) : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow(HelpState())
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
fun onEvent(event: HelpEvent) {
|
||||
when (event) {
|
||||
is HelpEvent.OnNavigateToBrowserPage -> {
|
||||
|
|
@ -30,6 +37,40 @@ class HelpViewModel @Inject constructor(
|
|||
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.core.tween
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -68,7 +67,7 @@ import ua.acclorite.book_story.presentation.ui.Transitions
|
|||
|
||||
@OptIn(
|
||||
ExperimentalMaterial3Api::class,
|
||||
ExperimentalMaterialApi::class, ExperimentalFoundationApi::class
|
||||
ExperimentalMaterialApi::class
|
||||
)
|
||||
@Composable
|
||||
fun HistoryScreen(
|
||||
|
|
@ -214,7 +213,7 @@ fun HistoryScreen(
|
|||
}
|
||||
|
||||
CategoryTitle(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
title = when (groupedHistory.title) {
|
||||
"today" -> stringResource(id = R.string.today)
|
||||
"yesterday" -> stringResource(id = R.string.yesterday)
|
||||
|
|
@ -230,7 +229,7 @@ fun HistoryScreen(
|
|||
groupedHistory.history, key = { it.id }
|
||||
) {
|
||||
HistoryItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
history = it,
|
||||
isOnClickEnabled = !state.isRefreshing,
|
||||
onBodyClick = {
|
||||
|
|
|
|||
|
|
@ -347,6 +347,7 @@ class LibraryViewModel @Inject constructor(
|
|||
query: String = if (_state.value.showSearch) _state.value.searchQuery else ""
|
||||
) {
|
||||
val books = getBooks.execute(query).map { book -> Pair(book, false) }
|
||||
|
||||
val categorizedBooks = books.groupBy {
|
||||
it.first.category
|
||||
}.toList().map {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,11 @@ fun ReaderScreen(
|
|||
val listState = rememberLazyListState()
|
||||
|
||||
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 {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPostScroll(
|
||||
|
|
@ -129,8 +133,8 @@ fun ReaderScreen(
|
|||
}
|
||||
)
|
||||
}
|
||||
LaunchedEffect(canScrollForward) {
|
||||
if (!canScrollForward && !state.showMenu && !loading) {
|
||||
LaunchedEffect(canScroll) {
|
||||
if (!canScroll && !state.showMenu && !loading) {
|
||||
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.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.MenuBook
|
||||
import androidx.compose.material.icons.filled.DisplaySettings
|
||||
|
|
@ -26,6 +25,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import ua.acclorite.book_story.R
|
||||
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.Screen
|
||||
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(
|
||||
navigator: Navigator
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
canScroll = {
|
||||
listState.canScrollForward || listState.canScrollBackward
|
||||
}
|
||||
)
|
||||
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
||||
|
||||
Scaffold(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
topBar = {
|
||||
|
|
@ -56,7 +51,7 @@ fun SettingsScreen(
|
|||
navigationIcon = {
|
||||
GoBackButton(navigator = navigator)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
scrollBehavior = scrollState.first,
|
||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
|
|
@ -68,7 +63,7 @@ fun SettingsScreen(
|
|||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding()),
|
||||
state = listState
|
||||
state = scrollState.second
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import ua.acclorite.book_story.presentation.components.CustomIconButton
|
|||
*/
|
||||
@Composable
|
||||
fun ColorPickerWithTitle(
|
||||
modifier: Modifier = Modifier,
|
||||
value: Color,
|
||||
title: String,
|
||||
horizontalPadding: Dp = 18.dp,
|
||||
|
|
@ -42,7 +43,7 @@ fun ColorPickerWithTitle(
|
|||
var color by remember { mutableStateOf(value) }
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.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.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
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.presentation.components.CategoryTitle
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SegmentedButtonWithTitle(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
buttons: List<ButtonItem>,
|
||||
enabled: Boolean,
|
||||
locked: Boolean,
|
||||
horizontalPadding: Dp = 18.dp,
|
||||
verticalPadding: Dp = 8.dp,
|
||||
onClick: (ButtonItem) -> Unit
|
||||
|
|
@ -32,7 +31,7 @@ fun SegmentedButtonWithTitle(
|
|||
val colors = SegmentedButtonDefaults.colors()
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = horizontalPadding, vertical = verticalPadding)
|
||||
) {
|
||||
|
|
@ -43,7 +42,7 @@ fun SegmentedButtonWithTitle(
|
|||
SingleChoiceSegmentedButtonRow {
|
||||
buttons.forEachIndexed { index, buttonItem ->
|
||||
SegmentedButton(
|
||||
enabled = enabled,
|
||||
enabled = locked,
|
||||
selected = buttonItem.selected,
|
||||
onClick = { onClick(buttonItem) },
|
||||
shape = when (index) {
|
||||
|
|
@ -60,9 +59,12 @@ fun SegmentedButtonWithTitle(
|
|||
else -> RoundedCornerShape(0)
|
||||
},
|
||||
colors = SegmentedButtonDefaults.colors(
|
||||
disabledInactiveBorderColor = colors.disabledActiveBorderColor,
|
||||
disabledInactiveContentColor = colors.disabledActiveContentColor,
|
||||
disabledActiveContainerColor = colors.activeContainerColor.copy(0.12f),
|
||||
disabledInactiveBorderColor = colors.activeBorderColor,
|
||||
disabledInactiveContentColor = colors.inactiveContentColor,
|
||||
disabledActiveContainerColor = colors.activeContainerColor,
|
||||
disabledActiveContentColor = colors.activeContentColor,
|
||||
disabledInactiveContainerColor = colors.inactiveContainerColor,
|
||||
disabledActiveBorderColor = colors.activeBorderColor,
|
||||
),
|
||||
label = {
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.navigationBars
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -16,26 +15,37 @@ import androidx.compose.material3.Scaffold
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ua.acclorite.book_story.R
|
||||
import ua.acclorite.book_story.domain.model.ButtonItem
|
||||
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||
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.MainViewModel
|
||||
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.SegmentedButtonWithTitle
|
||||
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.PureDark
|
||||
import ua.acclorite.book_story.presentation.ui.SlidingTransition
|
||||
import ua.acclorite.book_story.presentation.ui.Theme
|
||||
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)
|
||||
@Composable
|
||||
|
|
@ -43,19 +53,21 @@ fun AppearanceSettings(
|
|||
mainViewModel: MainViewModel,
|
||||
navigator: Navigator
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
canScroll = {
|
||||
listState.canScrollForward || listState.canScrollBackward
|
||||
}
|
||||
)
|
||||
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
||||
|
||||
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(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
topBar = {
|
||||
|
|
@ -66,7 +78,7 @@ fun AppearanceSettings(
|
|||
navigationIcon = {
|
||||
GoBackButton(navigator = navigator)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
scrollBehavior = scrollState.first,
|
||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
|
|
@ -78,25 +90,35 @@ fun AppearanceSettings(
|
|||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding()),
|
||||
state = listState
|
||||
state = scrollState.second
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.height(16.dp)
|
||||
)
|
||||
}
|
||||
item {
|
||||
CategoryTitle(
|
||||
modifier = Modifier.animateItem(),
|
||||
title = stringResource(id = R.string.theme_appearance_settings),
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.height(8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SegmentedButtonWithTitle(
|
||||
modifier = Modifier.animateItem(),
|
||||
title = stringResource(id = R.string.dark_theme_option),
|
||||
enabled = true,
|
||||
locked = true,
|
||||
buttons = DarkTheme.entries.map {
|
||||
ButtonItem(
|
||||
it.toString(),
|
||||
|
|
@ -119,9 +141,29 @@ fun AppearanceSettings(
|
|||
}
|
||||
|
||||
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(
|
||||
title = stringResource(id = R.string.theme_contrast_option),
|
||||
enabled = state.theme != Theme.DYNAMIC,
|
||||
locked = state.theme != Theme.DYNAMIC,
|
||||
buttons = ThemeContrast.entries.map {
|
||||
ButtonItem(
|
||||
it.toString(),
|
||||
|
|
@ -142,26 +184,63 @@ fun AppearanceSettings(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
AppearanceSettingsThemeSwitcher(mainViewModel = mainViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(22.dp))
|
||||
}
|
||||
item {
|
||||
CategoryTitle(
|
||||
title = stringResource(id = R.string.colors_appearance_settings),
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
SlidingTransition(
|
||||
modifier = Modifier.animateItem(
|
||||
fadeInSpec = null,
|
||||
fadeOutSpec = null
|
||||
),
|
||||
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 {
|
||||
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 {
|
||||
ColorPickerWithTitle(
|
||||
modifier = Modifier.animateItem(),
|
||||
value = Color(state.backgroundColor!!.toULong()),
|
||||
title = stringResource(id = R.string.background_color_option),
|
||||
onValueChange = {
|
||||
|
|
@ -175,6 +254,7 @@ fun AppearanceSettings(
|
|||
}
|
||||
item {
|
||||
ColorPickerWithTitle(
|
||||
modifier = Modifier.animateItem(),
|
||||
value = Color(state.fontColor!!.toULong()),
|
||||
title = stringResource(id = R.string.font_color_option),
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
|
|
@ -13,7 +14,9 @@ import androidx.compose.runtime.collectAsState
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
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.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.ui.Theme
|
||||
import ua.acclorite.book_story.presentation.ui.isDark
|
||||
import ua.acclorite.book_story.presentation.ui.isPureDark
|
||||
|
||||
/**
|
||||
* Theme switcher.
|
||||
*/
|
||||
@Composable
|
||||
fun AppearanceSettingsThemeSwitcher(
|
||||
mainViewModel: MainViewModel
|
||||
modifier: Modifier = Modifier,
|
||||
mainViewModel: MainViewModel,
|
||||
verticalPadding: Dp = 8.dp
|
||||
) {
|
||||
val state by mainViewModel.state.collectAsState()
|
||||
val themes = remember {
|
||||
|
|
@ -37,10 +43,10 @@ fun AppearanceSettingsThemeSwitcher(
|
|||
}
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = verticalPadding)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
CategoryTitle(
|
||||
title = stringResource(id = R.string.app_theme_option)
|
||||
)
|
||||
|
|
@ -60,6 +66,7 @@ fun AppearanceSettingsThemeSwitcher(
|
|||
theme = themeEntry,
|
||||
darkTheme = state.darkTheme!!.isDark(),
|
||||
themeContrast = state.themeContrast!!,
|
||||
isPureDark = state.pureDark!!.isPureDark(context = LocalContext.current),
|
||||
selected = state.theme == themeEntry.first
|
||||
) {
|
||||
mainViewModel.onEvent(MainEvent.OnChangeTheme(themeEntry.first.toString()))
|
||||
|
|
|
|||
|
|
@ -45,11 +45,17 @@ import ua.acclorite.book_story.presentation.ui.animatedColorScheme
|
|||
fun AppearanceSettingsThemeSwitcherItem(
|
||||
theme: Pair<Theme, UIText>,
|
||||
darkTheme: Boolean,
|
||||
isPureDark: Boolean,
|
||||
themeContrast: ThemeContrast,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val colorScheme = animatedColorScheme(theme.first, darkTheme, themeContrast)
|
||||
val colorScheme = animatedColorScheme(
|
||||
theme.first,
|
||||
darkTheme,
|
||||
isPureDark,
|
||||
themeContrast
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.navigationBars
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
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.util.Constants
|
||||
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.MainViewModel
|
||||
import ua.acclorite.book_story.presentation.data.Navigator
|
||||
|
|
@ -37,19 +37,13 @@ fun GeneralSettings(
|
|||
mainViewModel: MainViewModel,
|
||||
navigator: Navigator
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
canScroll = {
|
||||
listState.canScrollForward || listState.canScrollBackward
|
||||
}
|
||||
)
|
||||
|
||||
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
||||
val state by mainViewModel.state.collectAsState()
|
||||
|
||||
Scaffold(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
topBar = {
|
||||
|
|
@ -60,7 +54,7 @@ fun GeneralSettings(
|
|||
navigationIcon = {
|
||||
GoBackButton(navigator = navigator)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
scrollBehavior = scrollState.first,
|
||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
|
|
@ -72,7 +66,7 @@ fun GeneralSettings(
|
|||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding()),
|
||||
state = listState
|
||||
state = scrollState.second
|
||||
) {
|
||||
item {
|
||||
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.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
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.presentation.components.CategoryTitle
|
||||
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.MainViewModel
|
||||
import ua.acclorite.book_story.presentation.data.Navigator
|
||||
|
|
@ -42,12 +42,7 @@ fun ReaderSettings(
|
|||
mainViewModel: MainViewModel,
|
||||
navigator: Navigator
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
canScroll = {
|
||||
listState.canScrollForward || listState.canScrollBackward
|
||||
}
|
||||
)
|
||||
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
||||
|
||||
val state by mainViewModel.state.collectAsState()
|
||||
val fontFamily = remember(state.fontFamily) {
|
||||
|
|
@ -59,7 +54,7 @@ fun ReaderSettings(
|
|||
Scaffold(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
topBar = {
|
||||
|
|
@ -70,7 +65,7 @@ fun ReaderSettings(
|
|||
navigationIcon = {
|
||||
GoBackButton(navigator = navigator)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
scrollBehavior = scrollState.first,
|
||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
|
|
@ -82,7 +77,7 @@ fun ReaderSettings(
|
|||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding()),
|
||||
state = listState
|
||||
state = scrollState.second
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
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.greenTheme
|
||||
import ua.acclorite.book_story.presentation.ui.theme.pinkTheme
|
||||
|
|
@ -43,49 +44,61 @@ fun String.toTheme(): Theme {
|
|||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Composable
|
||||
fun colorScheme(theme: Theme, darkTheme: Boolean, themeContrast: ThemeContrast): ColorScheme {
|
||||
when (theme) {
|
||||
fun colorScheme(
|
||||
theme: Theme,
|
||||
darkTheme: Boolean,
|
||||
isPureDark: Boolean,
|
||||
themeContrast: ThemeContrast
|
||||
): ColorScheme {
|
||||
val colorScheme = when (theme) {
|
||||
Theme.DYNAMIC -> {
|
||||
/* Dynamic Theme */
|
||||
return if (darkTheme)
|
||||
if (darkTheme) {
|
||||
dynamicDarkColorScheme(LocalContext.current)
|
||||
else
|
||||
} else {
|
||||
dynamicLightColorScheme(LocalContext.current)
|
||||
}
|
||||
}
|
||||
|
||||
Theme.BLUE -> {
|
||||
/* Blue Theme */
|
||||
return blueTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||
blueTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||
}
|
||||
|
||||
Theme.PURPLE -> {
|
||||
/* Purple Theme */
|
||||
return purpleTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||
purpleTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||
}
|
||||
|
||||
Theme.GREEN -> {
|
||||
/* Green Theme */
|
||||
return greenTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||
greenTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||
}
|
||||
|
||||
Theme.PINK -> {
|
||||
/* Pink Theme */
|
||||
return pinkTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||
pinkTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||
}
|
||||
|
||||
Theme.YELLOW -> {
|
||||
/* Yellow Theme */
|
||||
return yellowTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||
yellowTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||
}
|
||||
|
||||
Theme.RED -> {
|
||||
/* Red Theme */
|
||||
return redTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||
redTheme(isDark = darkTheme, themeContrast = themeContrast)
|
||||
}
|
||||
|
||||
Theme.AQUA -> {
|
||||
/* 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
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
|
|
@ -16,6 +18,13 @@ import androidx.compose.ui.graphics.toArgb
|
|||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
@Immutable
|
||||
enum class PureDark {
|
||||
OFF,
|
||||
ON,
|
||||
SAVER
|
||||
}
|
||||
|
||||
@Immutable
|
||||
enum class ThemeContrast {
|
||||
STANDARD,
|
||||
|
|
@ -34,6 +43,10 @@ fun String.toDarkTheme(): DarkTheme {
|
|||
return DarkTheme.valueOf(this)
|
||||
}
|
||||
|
||||
fun String.toPureDark(): PureDark {
|
||||
return PureDark.valueOf(this)
|
||||
}
|
||||
|
||||
fun String.toThemeContrast(): ThemeContrast {
|
||||
return ThemeContrast.valueOf(this)
|
||||
}
|
||||
|
|
@ -48,9 +61,21 @@ fun DarkTheme.isDark(): Boolean {
|
|||
}
|
||||
|
||||
@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,
|
||||
isDark: Boolean,
|
||||
isPureDark: Boolean,
|
||||
themeContrast: ThemeContrast,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
|
|
@ -76,6 +101,7 @@ fun BooksHistoryResurrectionTheme(
|
|||
theme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) theme
|
||||
else if (theme != Theme.DYNAMIC) theme else Theme.BLUE,
|
||||
darkTheme = isDark,
|
||||
isPureDark = isPureDark,
|
||||
themeContrast = themeContrast
|
||||
)
|
||||
val animatedColorScheme = animateColorScheme(targetColorScheme = colorScheme)
|
||||
|
|
@ -143,11 +169,13 @@ private fun animateColorScheme(targetColorScheme: ColorScheme): ColorScheme {
|
|||
fun animatedColorScheme(
|
||||
theme: Theme,
|
||||
isDark: Boolean,
|
||||
isPureDark: Boolean,
|
||||
themeContrast: ThemeContrast
|
||||
): ColorScheme {
|
||||
val colorScheme = colorScheme(
|
||||
theme = theme,
|
||||
darkTheme = isDark,
|
||||
isPureDark = isPureDark,
|
||||
themeContrast = themeContrast
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import androidx.compose.animation.core.tween
|
|||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
|
|
@ -13,20 +15,20 @@ object Transitions {
|
|||
val DefaultTransitionIn = fadeIn(tween(300))
|
||||
val DefaultTransitionOut = fadeOut(tween(300))
|
||||
|
||||
val FadeTransitionIn = fadeIn(tween(300))
|
||||
val FadeTransitionOut = fadeOut(tween(50))
|
||||
val FadeTransitionIn = fadeIn(tween(350))
|
||||
val FadeTransitionOut = fadeOut(tween(100))
|
||||
|
||||
val SlidingTransitionIn = fadeIn(tween(300)) +
|
||||
slideInHorizontally(tween(300)) { it / 16 }
|
||||
val SlidingTransitionIn = fadeIn(tween(350)) +
|
||||
slideInHorizontally(tween(350)) { it / 16 }
|
||||
|
||||
val BackSlidingTransitionIn = fadeIn(tween(300)) +
|
||||
slideInHorizontally(tween(300)) { -it / 16 }
|
||||
val BackSlidingTransitionIn = fadeIn(tween(350)) +
|
||||
slideInHorizontally(tween(350)) { -it / 16 }
|
||||
|
||||
val SlidingTransitionOut = fadeOut(tween(200)) +
|
||||
slideOutHorizontally(tween(300)) { -it / 16 }
|
||||
val SlidingTransitionOut = fadeOut(tween(250)) +
|
||||
slideOutHorizontally(tween(350)) { -it / 16 }
|
||||
|
||||
val BackSlidingTransitionOut = fadeOut(tween(200)) +
|
||||
slideOutHorizontally(tween(300)) { it / 16 }
|
||||
val BackSlidingTransitionOut = fadeOut(tween(250)) +
|
||||
slideOutHorizontally(tween(350)) { it / 16 }
|
||||
}
|
||||
|
||||
@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">
|
||||
<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="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Basic Strings -->
|
||||
<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 -->
|
||||
<string name="library_screen">Бібліотека</string>
|
||||
<string name="history_screen">Історія</string>
|
||||
|
|
@ -108,7 +113,7 @@
|
|||
<string name="continue_read">Продовжити</string>
|
||||
<string name="never">Ніколи</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="undo">Скасувати</string>
|
||||
<string name="copy">Копіювати</string>
|
||||
|
|
@ -160,6 +165,7 @@
|
|||
<!-- Settings options -->
|
||||
<string name="language_option">Мова застосунка</string>
|
||||
<string name="dark_theme_option">Темна тема</string>
|
||||
<string name="pure_dark_option">Суцільна темрява (ОЛЕД)</string>
|
||||
<string name="theme_contrast_option">Контрастність теми</string>
|
||||
<string name="app_theme_option">Тема застосунка</string>
|
||||
<string name="font_family_option">Сімейство шрифтів</string>
|
||||
|
|
@ -181,6 +187,11 @@
|
|||
<string name="dark_theme_on">Ввімкнена</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 -->
|
||||
<string name="theme_contrast_standard">Типова</string>
|
||||
<string name="theme_contrast_medium">Середня</string>
|
||||
|
|
@ -257,10 +268,106 @@
|
|||
<string name="slava_ukraini_option_desc">Героям Слава! 🐉</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="clickable_note_1">Будь ласка, зверніть увагу, що на</string>
|
||||
<string name="clickable_note_2">такий виділений текст</string>
|
||||
<string name="clickable_note_3">можна натиснути.</string>
|
||||
<string name="clickable_note_action">На мене можна натиснути!</string>
|
||||
<string name="help_clickable_note_1">Будь ласка, зверніть увагу, що на</string>
|
||||
<string name="help_clickable_note_2">такий виділений текст</string>
|
||||
<string name="help_clickable_note_3">
|
||||
можна натиснути.
|
||||
Також ви можете приховати цю примітку, натиснувши на іконку, спробуйте!
|
||||
</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 -->
|
||||
<string name="default_font">За замовчуванням</string>
|
||||
|
|
@ -294,6 +401,11 @@
|
|||
<string name="app_icon_content_desc">Іконка застосунку</string>
|
||||
<string name="note_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>
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="InconsistentArrays">
|
||||
<!-- 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>
|
||||
|
||||
<!-- About sites -->
|
||||
<string name="releases_page" translatable="false">
|
||||
https://github.com/Acclorite/book-story/releases/latest
|
||||
</string>
|
||||
|
|
@ -18,6 +20,13 @@
|
|||
https://www.buymeacoffee.com/acclorite
|
||||
</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 -->
|
||||
<string name="library_screen">Library</string>
|
||||
<string name="history_screen">History</string>
|
||||
|
|
@ -123,7 +132,7 @@
|
|||
<string name="continue_read">Continue</string>
|
||||
<string name="never">Never</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="undo">Undo</string>
|
||||
<string name="copy">Copy</string>
|
||||
|
|
@ -175,6 +184,7 @@
|
|||
<!-- Settings options -->
|
||||
<string name="language_option">App language</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="app_theme_option">App theme</string>
|
||||
<string name="font_family_option">Font family</string>
|
||||
|
|
@ -196,6 +206,11 @@
|
|||
<string name="dark_theme_on">Enabled</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 -->
|
||||
<string name="theme_contrast_standard">Standard</string>
|
||||
<string name="theme_contrast_medium">Medium</string>
|
||||
|
|
@ -272,10 +287,105 @@
|
|||
<string name="slava_ukraini_option_desc">Heroyam Slava! 🐉</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="clickable_note_1">Please note, that</string>
|
||||
<string name="clickable_note_2">such highlighted text</string>
|
||||
<string name="clickable_note_3">can be clicked.</string>
|
||||
<string name="clickable_note_action">I\'m clickable!</string>
|
||||
<string name="help_clickable_note_1">Please note, that</string>
|
||||
<string name="help_clickable_note_2">such highlighted text</string>
|
||||
<string name="help_clickable_note_3">
|
||||
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 -->
|
||||
<string name="default_font">Default</string>
|
||||
|
|
@ -309,5 +419,10 @@
|
|||
<string name="app_icon_content_desc">App icon</string>
|
||||
<string name="note_content_desc">Note</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>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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:windowContentTransitions">false</item>
|
||||
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<style name="Theme.Start.Splash" parent="Theme.SplashScreen">
|
||||
<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="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue