0.7.0 - Reader Screen
This commit is contained in:
parent
03b1ef48c2
commit
0b3a31a183
63 changed files with 2475 additions and 468 deletions
|
|
@ -28,34 +28,15 @@ android {
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
getByName("release") {
|
getByName("release") {
|
||||||
// Enables code shrinking, obfuscation, and optimization for only
|
|
||||||
// your project's release build type. Make sure to use a build
|
|
||||||
// variant with `isDebuggable=false`.
|
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
|
|
||||||
// Enables resource shrinking, which is performed by the
|
|
||||||
// Android Gradle plugin.
|
|
||||||
isShrinkResources = true
|
isShrinkResources = true
|
||||||
|
|
||||||
// Includes the default ProGuard rules files that are packaged with
|
|
||||||
// the Android Gradle plugin. To learn more, go to the section about
|
|
||||||
// R8 configuration files.
|
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
)
|
)
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// release {
|
|
||||||
// isMinifyEnabled = false
|
|
||||||
// proguardFiles(
|
|
||||||
// getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
||||||
// "proguard-rules.pro"
|
|
||||||
// )
|
|
||||||
// signingConfig = signingConfigs.getByName("debug")
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,13 @@ import com.acclorite.books_history.domain.model.Book
|
||||||
import com.acclorite.books_history.presentation.NavigationHost
|
import com.acclorite.books_history.presentation.NavigationHost
|
||||||
import com.acclorite.books_history.presentation.Screen
|
import com.acclorite.books_history.presentation.Screen
|
||||||
import com.acclorite.books_history.presentation.components.BottomNavigationBar
|
import com.acclorite.books_history.presentation.components.BottomNavigationBar
|
||||||
import com.acclorite.books_history.presentation.main.MainEvent
|
import com.acclorite.books_history.presentation.data.MainEvent
|
||||||
import com.acclorite.books_history.presentation.main.MainViewModel
|
import com.acclorite.books_history.presentation.data.MainViewModel
|
||||||
import com.acclorite.books_history.presentation.screens.book_info.BookInfoScreen
|
import com.acclorite.books_history.presentation.screens.book_info.BookInfoScreen
|
||||||
import com.acclorite.books_history.presentation.screens.browse.BrowseScreen
|
import com.acclorite.books_history.presentation.screens.browse.BrowseScreen
|
||||||
|
import com.acclorite.books_history.presentation.screens.history.HistoryScreen
|
||||||
import com.acclorite.books_history.presentation.screens.library.LibraryScreen
|
import com.acclorite.books_history.presentation.screens.library.LibraryScreen
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.ReaderScreen
|
||||||
import com.acclorite.books_history.presentation.screens.settings.SettingsScreen
|
import com.acclorite.books_history.presentation.screens.settings.SettingsScreen
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.appearance.AppearanceSettings
|
import com.acclorite.books_history.presentation.screens.settings.nested.appearance.AppearanceSettings
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.general.GeneralSettings
|
import com.acclorite.books_history.presentation.screens.settings.nested.general.GeneralSettings
|
||||||
|
|
@ -93,6 +95,10 @@ class MainActivity : ComponentActivity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composable(screen = Screen.HISTORY) {
|
||||||
|
HistoryScreen(navigator = this@NavigationHost)
|
||||||
|
}
|
||||||
|
|
||||||
composable(screen = Screen.BROWSE) {
|
composable(screen = Screen.BROWSE) {
|
||||||
BrowseScreen(navigator = this@NavigationHost)
|
BrowseScreen(navigator = this@NavigationHost)
|
||||||
}
|
}
|
||||||
|
|
@ -106,11 +112,20 @@ class MainActivity : ComponentActivity() {
|
||||||
enterAnim = Transitions.SlidingTransitionIn,
|
enterAnim = Transitions.SlidingTransitionIn,
|
||||||
exitAnim = Transitions.SlidingTransitionOut
|
exitAnim = Transitions.SlidingTransitionOut
|
||||||
) {
|
) {
|
||||||
|
|
||||||
BookInfoScreen(
|
BookInfoScreen(
|
||||||
navigator = this@NavigationHost
|
navigator = this@NavigationHost
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
composable(
|
||||||
|
screen = Screen.READER,
|
||||||
|
enterAnim = Transitions.SlidingTransitionIn,
|
||||||
|
exitAnim = Transitions.SlidingTransitionOut
|
||||||
|
) {
|
||||||
|
ReaderScreen(
|
||||||
|
navigator = this@NavigationHost,
|
||||||
|
mainViewModel = mainViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
composable(
|
composable(
|
||||||
|
|
@ -119,7 +134,6 @@ class MainActivity : ComponentActivity() {
|
||||||
exitAnim = Transitions.SlidingTransitionOut
|
exitAnim = Transitions.SlidingTransitionOut
|
||||||
) {
|
) {
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
viewModel = mainViewModel,
|
|
||||||
navigator = this@NavigationHost
|
navigator = this@NavigationHost
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.acclorite.books_history.domain.model
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class ChipItem(
|
||||||
|
val id: String,
|
||||||
|
val title: String,
|
||||||
|
val textStyle: TextStyle,
|
||||||
|
val selected: Boolean
|
||||||
|
)
|
||||||
|
|
@ -2,9 +2,11 @@ package com.acclorite.books_history.domain.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import com.acclorite.books_history.util.UIText
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class FontWithName(
|
data class FontWithName(
|
||||||
val fontName: String,
|
val id: String,
|
||||||
|
val fontName: UIText,
|
||||||
val font: FontFamily
|
val font: FontFamily
|
||||||
)
|
)
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.acclorite.books_history.domain.use_case
|
||||||
|
|
||||||
|
import com.acclorite.books_history.domain.model.StringWithId
|
||||||
|
import com.acclorite.books_history.domain.repository.BookRepository
|
||||||
|
import com.acclorite.books_history.util.Resource
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetText @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
|
suspend fun execute(file: File): Flow<Resource<List<StringWithId>>> {
|
||||||
|
return repository.getBookTextFromFile(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -75,7 +75,7 @@ class Navigator @AssistedInject constructor(
|
||||||
private val backStack = savedStateHandle.getStateFlow(BACKSTACK, mutableListOf<Screen>())
|
private val backStack = savedStateHandle.getStateFlow(BACKSTACK, mutableListOf<Screen>())
|
||||||
private val arguments = mutableStateListOf<Argument>()
|
private val arguments = mutableStateListOf<Argument>()
|
||||||
|
|
||||||
private fun putArgument(argument: Argument) {
|
fun putArgument(argument: Argument) {
|
||||||
var found = false
|
var found = false
|
||||||
|
|
||||||
for ((index, arg) in arguments.withIndex()) {
|
for ((index, arg) in arguments.withIndex()) {
|
||||||
|
|
@ -115,6 +115,19 @@ class Navigator @AssistedInject constructor(
|
||||||
savedStateHandle[CURRENT_SCREEN] = screen
|
savedStateHandle[CURRENT_SCREEN] = screen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun navigateWithoutBackStack(screen: Screen, vararg args: Argument) =
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
if (backStack.value.last() == screen) {
|
||||||
|
backStack.value.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
args.forEach {
|
||||||
|
putArgument(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
savedStateHandle[CURRENT_SCREEN] = screen
|
||||||
|
}
|
||||||
|
|
||||||
fun navigateBack() = viewModelScope.launch(Dispatchers.Default) {
|
fun navigateBack() = viewModelScope.launch(Dispatchers.Default) {
|
||||||
if (canGoBack()) {
|
if (canGoBack()) {
|
||||||
savedStateHandle[CURRENT_SCREEN] = backStack.value.last()
|
savedStateHandle[CURRENT_SCREEN] = backStack.value.last()
|
||||||
|
|
@ -126,10 +139,6 @@ class Navigator @AssistedInject constructor(
|
||||||
return backStack.value.isNotEmpty()
|
return backStack.value.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearBackStack() {
|
|
||||||
backStack.value.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCurrentScreen(): StateFlow<Screen> {
|
fun getCurrentScreen(): StateFlow<Screen> {
|
||||||
return currentScreen
|
return currentScreen
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.acclorite.books_history.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.LineBreak
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CategoryTitle(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
title: String,
|
||||||
|
color: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
padding: Dp = 18.dp
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
color = color,
|
||||||
|
modifier = modifier.padding(horizontal = padding),
|
||||||
|
style = TextStyle(
|
||||||
|
lineBreak = LineBreak.Heading,
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 20.sp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
|
@ -60,6 +61,7 @@ fun CustomDialogWithContent(
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
.navigationBarsPadding()
|
.navigationBarsPadding()
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
.clip(MaterialTheme.shapes.extraLarge)
|
.clip(MaterialTheme.shapes.extraLarge)
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ fun CustomDialogWithLazyColumn(
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
.navigationBarsPadding()
|
.navigationBarsPadding()
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
.clip(MaterialTheme.shapes.extraLarge)
|
.clip(MaterialTheme.shapes.extraLarge)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.acclorite.books_history.util
|
package com.acclorite.books_history.presentation.data
|
||||||
|
|
||||||
fun String.removeTrailingZero(): String {
|
fun String.removeTrailingZero(): String {
|
||||||
if (!this.contains('.'))
|
if (!this.contains('.'))
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.acclorite.books_history.presentation.data
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
|
||||||
|
sealed class MainEvent {
|
||||||
|
data class OnLocaleUpdate(val activity: ComponentActivity) : MainEvent()
|
||||||
|
data class OnChangeLanguage(val lang: String, val activity: ComponentActivity) : MainEvent()
|
||||||
|
data class OnChangeTheme(val theme: String) : MainEvent()
|
||||||
|
data class OnChangeDarkTheme(val darkTheme: String) : MainEvent()
|
||||||
|
data class OnChangeFontFamily(val fontFamily: String) : MainEvent()
|
||||||
|
data class OnChangeFontStyle(val fontStyle: Boolean) : MainEvent()
|
||||||
|
data class OnChangeFontSize(val fontSize: Int) : MainEvent()
|
||||||
|
data class OnChangeLineHeight(val lineHeight: Int) : MainEvent()
|
||||||
|
data class OnChangeParagraphHeight(val paragraphHeight: Int) : MainEvent()
|
||||||
|
data class OnChangeParagraphIndentation(val bool: Boolean) : MainEvent()
|
||||||
|
data class OnChangeBackgroundColor(val color: Long) : MainEvent()
|
||||||
|
data class OnChangeFontColor(val color: Long) : MainEvent()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.acclorite.books_history.presentation.main
|
package com.acclorite.books_history.presentation.data
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
|
@ -109,7 +109,7 @@ class MainViewModel @Inject constructor(
|
||||||
|
|
||||||
/* -- Font Family -------------------------------------------------- */
|
/* -- Font Family -------------------------------------------------- */
|
||||||
private var _fontFamily: String =
|
private var _fontFamily: String =
|
||||||
stateHandle[Constants.FONT] ?: Constants.FONTS[0].fontName
|
stateHandle[Constants.FONT] ?: Constants.FONTS[0].id
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
stateHandle[Constants.FONT] = value
|
stateHandle[Constants.FONT] = value
|
||||||
|
|
@ -204,6 +204,63 @@ class MainViewModel @Inject constructor(
|
||||||
_theme = event.theme.toTheme()
|
_theme = event.theme.toTheme()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeFontFamily -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(DataStoreConstants.FONT, event.fontFamily)
|
||||||
|
_fontFamily = Constants.FONTS.find { font -> font.id == event.fontFamily }?.id
|
||||||
|
?: Constants.FONTS[0].id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeFontStyle -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(DataStoreConstants.IS_ITALIC, event.fontStyle)
|
||||||
|
_isItalic = event.fontStyle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeFontSize -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(DataStoreConstants.FONT_SIZE, event.fontSize)
|
||||||
|
_fontSize = event.fontSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeLineHeight -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(DataStoreConstants.LINE_HEIGHT, event.lineHeight)
|
||||||
|
_lineHeight = event.lineHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeParagraphHeight -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(DataStoreConstants.PARAGRAPH_HEIGHT, event.paragraphHeight)
|
||||||
|
_paragraphHeight = event.paragraphHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeParagraphIndentation -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(DataStoreConstants.PARAGRAPH_INDENTATION, event.bool)
|
||||||
|
_paragraphIndentation = event.bool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeBackgroundColor -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(DataStoreConstants.BACKGROUND_COLOR, event.color)
|
||||||
|
_backgroundColor = event.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeFontColor -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(DataStoreConstants.FONT_COLOR, event.color)
|
||||||
|
_fontColor = event.color
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,9 +328,7 @@ class MainViewModel @Inject constructor(
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.BACKGROUND_COLOR, _backgroundColor)
|
.execute(DataStoreConstants.BACKGROUND_COLOR, _backgroundColor)
|
||||||
.first {
|
.first {
|
||||||
setDatastore.execute(DataStoreConstants.BACKGROUND_COLOR, it)
|
onEvent(MainEvent.OnChangeBackgroundColor(it))
|
||||||
_backgroundColor = it
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -283,9 +338,7 @@ class MainViewModel @Inject constructor(
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.FONT_COLOR, _fontColor)
|
.execute(DataStoreConstants.FONT_COLOR, _fontColor)
|
||||||
.first {
|
.first {
|
||||||
setDatastore.execute(DataStoreConstants.FONT_COLOR, it)
|
onEvent(MainEvent.OnChangeFontColor(it))
|
||||||
_fontColor = it
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -295,10 +348,7 @@ class MainViewModel @Inject constructor(
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.FONT, _fontFamily)
|
.execute(DataStoreConstants.FONT, _fontFamily)
|
||||||
.first {
|
.first {
|
||||||
setDatastore.execute(DataStoreConstants.FONT, it)
|
onEvent(MainEvent.OnChangeFontFamily(it))
|
||||||
_fontFamily = Constants.FONTS.find { font -> font.fontName == it }?.fontName
|
|
||||||
?: Constants.FONTS[0].fontName
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -308,9 +358,7 @@ class MainViewModel @Inject constructor(
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.IS_ITALIC, _isItalic)
|
.execute(DataStoreConstants.IS_ITALIC, _isItalic)
|
||||||
.first {
|
.first {
|
||||||
setDatastore.execute(DataStoreConstants.IS_ITALIC, it)
|
onEvent(MainEvent.OnChangeFontStyle(it))
|
||||||
_isItalic = it
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -320,9 +368,7 @@ class MainViewModel @Inject constructor(
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.FONT_SIZE, _fontSize)
|
.execute(DataStoreConstants.FONT_SIZE, _fontSize)
|
||||||
.first {
|
.first {
|
||||||
setDatastore.execute(DataStoreConstants.FONT_SIZE, it)
|
onEvent(MainEvent.OnChangeFontSize(it))
|
||||||
_fontSize = it
|
|
||||||
|
|
||||||
it > 0
|
it > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -332,9 +378,7 @@ class MainViewModel @Inject constructor(
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.LINE_HEIGHT, _lineHeight)
|
.execute(DataStoreConstants.LINE_HEIGHT, _lineHeight)
|
||||||
.first {
|
.first {
|
||||||
setDatastore.execute(DataStoreConstants.LINE_HEIGHT, it)
|
onEvent(MainEvent.OnChangeLineHeight(it))
|
||||||
_lineHeight = it
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -344,9 +388,7 @@ class MainViewModel @Inject constructor(
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.PARAGRAPH_HEIGHT, _paragraphHeight)
|
.execute(DataStoreConstants.PARAGRAPH_HEIGHT, _paragraphHeight)
|
||||||
.first {
|
.first {
|
||||||
setDatastore.execute(DataStoreConstants.PARAGRAPH_HEIGHT, it)
|
onEvent(MainEvent.OnChangeParagraphHeight(it))
|
||||||
_paragraphHeight = it
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -356,9 +398,7 @@ class MainViewModel @Inject constructor(
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.PARAGRAPH_INDENTATION, _paragraphIndentation)
|
.execute(DataStoreConstants.PARAGRAPH_INDENTATION, _paragraphIndentation)
|
||||||
.first {
|
.first {
|
||||||
setDatastore.execute(DataStoreConstants.PARAGRAPH_INDENTATION, it)
|
onEvent(MainEvent.OnChangeParagraphIndentation(it))
|
||||||
_paragraphIndentation = it
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
package com.acclorite.books_history.presentation.main
|
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
|
|
||||||
sealed class MainEvent() {
|
|
||||||
data class OnChangeLanguage(val lang: String, val activity: ComponentActivity) : MainEvent()
|
|
||||||
data class OnLocaleUpdate(val activity: ComponentActivity) : MainEvent()
|
|
||||||
data class OnChangeTheme(val theme: String) : MainEvent()
|
|
||||||
data class OnChangeDarkTheme(val darkTheme: String) : MainEvent()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -12,14 +12,15 @@ import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Done
|
import androidx.compose.material.icons.filled.Done
|
||||||
|
|
@ -37,7 +38,9 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
@ -45,6 +48,7 @@ import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.acclorite.books_history.R
|
import com.acclorite.books_history.R
|
||||||
|
|
@ -95,7 +99,9 @@ fun BookInfoScreen(
|
||||||
|
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||||
val scrollState = rememberScrollState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
val firstVisibleItemIndex by remember { derivedStateOf { listState.firstVisibleItemIndex } }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.init(
|
viewModel.init(
|
||||||
|
|
@ -154,7 +160,17 @@ fun BookInfoScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {},
|
title = {
|
||||||
|
DefaultTransition(visible = firstVisibleItemIndex > 0) {
|
||||||
|
Text(
|
||||||
|
state.book.title,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
actions = {
|
actions = {
|
||||||
Box {
|
Box {
|
||||||
DefaultTransition(
|
DefaultTransition(
|
||||||
|
|
@ -206,33 +222,43 @@ fun BookInfoScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
if (state.book.coverImage != null) {
|
|
||||||
BookInfoBackground(image = state.book.coverImage!!.asImageBitmap())
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = paddingValues.calculateTopPadding())
|
|
||||||
) {
|
) {
|
||||||
Column(
|
LazyColumn(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize(),
|
||||||
.verticalScroll(scrollState)
|
state = listState
|
||||||
.padding(horizontal = 24.dp)
|
|
||||||
) {
|
) {
|
||||||
|
item {
|
||||||
|
Box(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
if (state.book.coverImage != null) {
|
||||||
|
BookInfoBackground(
|
||||||
|
height = paddingValues.calculateTopPadding() + 12.dp + 195.dp,
|
||||||
|
image = state.book.coverImage!!.asImageBitmap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Column(Modifier.fillMaxWidth()) {
|
||||||
// Info
|
Spacer(modifier = Modifier.height(paddingValues.calculateTopPadding() + 12.dp))
|
||||||
BookInfoInfoSection(viewModel = viewModel, book = state.book)
|
// Info
|
||||||
|
BookInfoInfoSection(viewModel = viewModel, book = state.book)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
item {
|
||||||
// Statistic
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
BookInfoStatisticSection(book = state.book)
|
// Statistic
|
||||||
|
BookInfoStatisticSection(book = state.book)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
item {
|
||||||
// Description
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
BookInfoDescriptionSection(book = state.book)
|
// Description
|
||||||
|
BookInfoDescriptionSection(book = state.book)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.book.file != null) {
|
if (state.book.file != null) {
|
||||||
|
|
@ -260,7 +286,7 @@ fun BookInfoScreen(
|
||||||
Modifier.size(24.dp)
|
Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = scrollState.value < 5
|
visible = !listState.canScrollBackward
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(
|
text = stringResource(
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,41 @@
|
||||||
package com.acclorite.books_history.presentation.screens.book_info.components
|
package com.acclorite.books_history.presentation.screens.book_info.components
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.blur
|
import androidx.compose.ui.draw.blur
|
||||||
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoBackground(image: ImageBitmap) {
|
fun BookInfoBackground(height: Dp, image: ImageBitmap) {
|
||||||
|
val background = MaterialTheme.colorScheme.background
|
||||||
|
|
||||||
Image(
|
Image(
|
||||||
bitmap = image,
|
bitmap = image,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.fillMaxHeight(0.5f)
|
.height(height)
|
||||||
.blur(1.5.dp),
|
.drawWithContent {
|
||||||
|
drawContent()
|
||||||
|
drawRect(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
0f to Color.Transparent,
|
||||||
|
0.8f to background
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.blur(2.dp),
|
||||||
alpha = 0.3f,
|
alpha = 0.3f,
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.fillMaxHeight(0.51f)
|
|
||||||
.background(
|
|
||||||
Brush.verticalGradient(
|
|
||||||
listOf(
|
|
||||||
Color.Transparent,
|
|
||||||
MaterialTheme.colorScheme.surface
|
|
||||||
),
|
|
||||||
startY = 0f,
|
|
||||||
endY = 750f
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import androidx.activity.result.PickVisualMediaRequest
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.HideImage
|
import androidx.compose.material.icons.filled.HideImage
|
||||||
|
|
@ -13,6 +14,7 @@ import androidx.compose.material.icons.filled.ImageSearch
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -61,9 +63,11 @@ fun BookInfoChangeCoverBottomSheet(
|
||||||
)
|
)
|
||||||
|
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
viewModel.onEvent(BookInfoEvent.OnShowHideChangeCoverBottomSheet)
|
viewModel.onEvent(BookInfoEvent.OnShowHideChangeCoverBottomSheet)
|
||||||
},
|
},
|
||||||
|
sheetState = rememberModalBottomSheetState(true),
|
||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
containerColor = MaterialTheme.elevation(ElevationDefaults.BottomSheet)
|
containerColor = MaterialTheme.elevation(ElevationDefaults.BottomSheet)
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.acclorite.books_history.presentation.screens.book_info.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
|
@ -21,7 +22,8 @@ fun BookInfoDescriptionSection(book: Book) {
|
||||||
?: stringResource(id = R.string.error_no_description),
|
?: stringResource(id = R.string.error_no_description),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
textAlign = TextAlign.Start
|
textAlign = TextAlign.Start,
|
||||||
|
modifier = Modifier.padding(horizontal = 24.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ package com.acclorite.books_history.presentation.screens.book_info.components
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -31,7 +33,7 @@ fun BookInfoDetailsBottomSheet(
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val pattern = SimpleDateFormat("HH:mm dd MMM", Locale.getDefault())
|
val pattern = SimpleDateFormat("HH:mm dd MMM yyyy", Locale.getDefault())
|
||||||
val lastOpened = pattern.format(Date(state.book.lastOpened ?: 0))
|
val lastOpened = pattern.format(Date(state.book.lastOpened ?: 0))
|
||||||
|
|
||||||
val sizeBytes = state.book.file?.length() ?: 0
|
val sizeBytes = state.book.file?.length() ?: 0
|
||||||
|
|
@ -46,9 +48,11 @@ fun BookInfoDetailsBottomSheet(
|
||||||
|
|
||||||
|
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
viewModel.onEvent(BookInfoEvent.OnShowHideDetailsBottomSheet)
|
viewModel.onEvent(BookInfoEvent.OnShowHideDetailsBottomSheet)
|
||||||
},
|
},
|
||||||
|
sheetState = rememberModalBottomSheetState(true),
|
||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
containerColor = MaterialTheme.elevation(ElevationDefaults.BottomSheet)
|
containerColor = MaterialTheme.elevation(ElevationDefaults.BottomSheet)
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
|
@ -54,6 +55,7 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.acclorite.books_history.presentation.screens.book_info.components
|
package com.acclorite.books_history.presentation.screens.book_info.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
|
@ -14,48 +16,54 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.acclorite.books_history.R
|
import com.acclorite.books_history.R
|
||||||
import com.acclorite.books_history.domain.model.Book
|
import com.acclorite.books_history.domain.model.Book
|
||||||
import com.acclorite.books_history.util.removeDigits
|
import com.acclorite.books_history.presentation.data.removeDigits
|
||||||
import com.acclorite.books_history.util.removeTrailingZero
|
import com.acclorite.books_history.presentation.data.removeTrailingZero
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoStatisticSection(book: Book) {
|
fun BookInfoStatisticSection(book: Book) {
|
||||||
Text(
|
Column(
|
||||||
text = "${
|
Modifier
|
||||||
(book.progress * 100)
|
.fillMaxWidth()
|
||||||
.toDouble()
|
.padding(horizontal = 24.dp)
|
||||||
.removeDigits(1)
|
) {
|
||||||
.removeTrailingZero()
|
Text(
|
||||||
}%",
|
text = "${
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
(book.progress * 100)
|
||||||
style = MaterialTheme.typography.bodyLarge
|
.toDouble()
|
||||||
)
|
.removeDigits(1)
|
||||||
|
.removeTrailingZero()
|
||||||
|
}%",
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
book.progress.coerceAtLeast(0.01f),
|
book.progress.coerceAtLeast(0.01f),
|
||||||
strokeCap = StrokeCap.Round,
|
strokeCap = StrokeCap.Round,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
trackColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(0.05f)
|
trackColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(0.05f)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(
|
text = stringResource(
|
||||||
id = R.string.you_read_query,
|
id = R.string.you_read_query,
|
||||||
(book.progress * 100)
|
(book.progress * 100)
|
||||||
.toDouble()
|
.toDouble()
|
||||||
.removeDigits(1)
|
.removeDigits(1)
|
||||||
.removeTrailingZero() + "%"
|
.removeTrailingZero() + "%"
|
||||||
) + " " + stringResource(
|
) + " " + stringResource(
|
||||||
if (book.progress > 0.2f) R.string.read_keep
|
if (book.progress > 0.2f) R.string.read_keep
|
||||||
else R.string.read_more
|
else R.string.read_more
|
||||||
),
|
),
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.PermissionState
|
import com.google.accompanist.permissions.PermissionState
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
sealed class BrowseEvent() {
|
sealed class BrowseEvent {
|
||||||
data class OnStoragePermissionRequest(val activity: ComponentActivity) : BrowseEvent()
|
data class OnStoragePermissionRequest(val activity: ComponentActivity) : BrowseEvent()
|
||||||
data class OnLegacyStoragePermissionRequest(val permissionState: PermissionState) :
|
data class OnLegacyStoragePermissionRequest(val permissionState: PermissionState) :
|
||||||
BrowseEvent()
|
BrowseEvent()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.history
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.acclorite.books_history.presentation.Navigator
|
||||||
|
import com.acclorite.books_history.presentation.screens.history.data.HistoryViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HistoryScreen(
|
||||||
|
viewModel: HistoryViewModel = hiltViewModel(),
|
||||||
|
navigator: Navigator
|
||||||
|
) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.history.data
|
||||||
|
|
||||||
|
sealed class HistoryEvent
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.history.data
|
||||||
|
|
||||||
|
data class HistoryState(
|
||||||
|
val foo: Int = 0
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.history.data
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class HistoryViewModel @Inject constructor(
|
||||||
|
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _state = MutableStateFlow(HistoryState())
|
||||||
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
|
fun onEvent(event: HistoryEvent) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -343,7 +343,12 @@ fun LibraryScreen(
|
||||||
viewModel.onEvent(LibraryEvent.OnSelectBook(it, true))
|
viewModel.onEvent(LibraryEvent.OnSelectBook(it, true))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onButtonClick = { navigator.navigate(Screen.READER) }
|
onButtonClick = {
|
||||||
|
navigator.navigate(
|
||||||
|
Screen.READER,
|
||||||
|
Argument("book", it.first)
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@ import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.acclorite.books_history.domain.model.Book
|
import com.acclorite.books_history.domain.model.Book
|
||||||
|
import com.acclorite.books_history.presentation.data.removeDigits
|
||||||
|
import com.acclorite.books_history.presentation.data.removeTrailingZero
|
||||||
import com.acclorite.books_history.ui.elevation
|
import com.acclorite.books_history.ui.elevation
|
||||||
import com.acclorite.books_history.util.removeDigits
|
|
||||||
import com.acclorite.books_history.util.removeTrailingZero
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,5 @@ sealed class LibraryEvent {
|
||||||
data class OnMoveBooks(val pagerState: PagerState) : LibraryEvent()
|
data class OnMoveBooks(val pagerState: PagerState) : LibraryEvent()
|
||||||
data object OnShowHideDeleteDialog : LibraryEvent()
|
data object OnShowHideDeleteDialog : LibraryEvent()
|
||||||
data object OnDeleteBooks : LibraryEvent()
|
data object OnDeleteBooks : LibraryEvent()
|
||||||
|
data class OnUpdateBook(val book: Book) : LibraryEvent()
|
||||||
}
|
}
|
||||||
|
|
@ -257,6 +257,21 @@ class LibraryViewModel @Inject constructor(
|
||||||
onEvent(LibraryEvent.OnClearSelectedBooks)
|
onEvent(LibraryEvent.OnClearSelectedBooks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is LibraryEvent.OnUpdateBook -> {
|
||||||
|
val books = _state.value.books.toMutableList()
|
||||||
|
|
||||||
|
val index = books.indexOfFirst { it.first.id == event.book.id }
|
||||||
|
if (index != -1) {
|
||||||
|
books[index] = Pair(event.book, books[index].second)
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
books = books
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,324 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.reader
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.text.selection.DisableSelection
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.material.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
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.runtime.snapshotFlow
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.style.LineBreak
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.acclorite.books_history.R
|
||||||
|
import com.acclorite.books_history.domain.model.Book
|
||||||
|
import com.acclorite.books_history.presentation.Navigator
|
||||||
|
import com.acclorite.books_history.presentation.components.IsError
|
||||||
|
import com.acclorite.books_history.presentation.data.MainViewModel
|
||||||
|
import com.acclorite.books_history.presentation.screens.library.data.LibraryEvent
|
||||||
|
import com.acclorite.books_history.presentation.screens.library.data.LibraryViewModel
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.components.ReaderBottomBar
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.components.ReaderEndItem
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.components.ReaderSettingsBottomSheet
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.components.ReaderStartItem
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.components.ReaderTopBar
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderEvent
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderViewModel
|
||||||
|
import com.acclorite.books_history.ui.elevation
|
||||||
|
import com.acclorite.books_history.util.Constants
|
||||||
|
import dagger.hilt.android.lifecycle.withCreationCallback
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
|
@Composable
|
||||||
|
fun ReaderScreen(
|
||||||
|
mainViewModel: MainViewModel,
|
||||||
|
navigator: Navigator
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current as ComponentActivity
|
||||||
|
val systemBarsColor = MaterialTheme.elevation(20.dp).copy(0.85f)
|
||||||
|
|
||||||
|
val viewModel by context.viewModels<ReaderViewModel>(
|
||||||
|
extrasProducer = {
|
||||||
|
val book = navigator.retrieveArgument("book") as? Book
|
||||||
|
if (book == null) {
|
||||||
|
navigator.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
context.defaultViewModelCreationExtras
|
||||||
|
.withCreationCallback<ReaderViewModel.Factory> { factory ->
|
||||||
|
factory.create(book ?: Constants.EMPTY_BOOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val libraryViewModel: LibraryViewModel = hiltViewModel()
|
||||||
|
|
||||||
|
var loading by remember { mutableStateOf(true) }
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val nestedScrollConnection = remember {
|
||||||
|
object : NestedScrollConnection {
|
||||||
|
override fun onPostScroll(
|
||||||
|
consumed: Offset,
|
||||||
|
available: Offset,
|
||||||
|
source: NestedScrollSource
|
||||||
|
): Offset {
|
||||||
|
val velocity = consumed.y
|
||||||
|
|
||||||
|
if ((velocity > 70 || velocity < -70) && state.showMenu) {
|
||||||
|
viewModel.onEvent(ReaderEvent.OnShowHideMenu(context = context))
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onPostScroll(consumed, available, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fontFamily = Constants.FONTS.find {
|
||||||
|
it.id == mainViewModel.fontFamily.collectAsState().value!!
|
||||||
|
} ?: Constants.FONTS[0]
|
||||||
|
val fontStyle = when (mainViewModel.isItalic.collectAsState().value!!) {
|
||||||
|
false -> FontStyle.Normal
|
||||||
|
true -> FontStyle.Italic
|
||||||
|
}
|
||||||
|
val fontSize = mainViewModel.fontSize.collectAsState().value!!
|
||||||
|
val lineHeight = mainViewModel.lineHeight.collectAsState().value!!
|
||||||
|
val paragraphHeight = mainViewModel.paragraphHeight.collectAsState().value!!
|
||||||
|
val paragraphIndentation = mainViewModel.paragraphIndentation.collectAsState().value!!
|
||||||
|
val backgroundColor = mainViewModel.backgroundColor.collectAsState().value!!
|
||||||
|
val fontColor = mainViewModel.fontColor.collectAsState().value!!
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.init(
|
||||||
|
navigator,
|
||||||
|
context,
|
||||||
|
listState,
|
||||||
|
refreshList = {
|
||||||
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
|
},
|
||||||
|
onLoaded = {
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
LaunchedEffect(listState) {
|
||||||
|
snapshotFlow {
|
||||||
|
listState.firstVisibleItemIndex
|
||||||
|
}.debounce(50).collectLatest {
|
||||||
|
if (!loading) {
|
||||||
|
val progress = if (it > 0) {
|
||||||
|
if ((it + listState.layoutInfo.visibleItemsInfo.size) >= state.book.text.lastIndex) {
|
||||||
|
1f
|
||||||
|
} else {
|
||||||
|
(it.toFloat() / (state.book.text.size - 1).toFloat())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.onEvent(
|
||||||
|
ReaderEvent.OnChangeProgress(
|
||||||
|
progress,
|
||||||
|
navigator,
|
||||||
|
refreshList = { book ->
|
||||||
|
libraryViewModel.onEvent(
|
||||||
|
LibraryEvent.OnUpdateBook(
|
||||||
|
book
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.showSettingsBottomSheet) {
|
||||||
|
ReaderSettingsBottomSheet(viewModel = viewModel, mainViewModel = mainViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.nestedScroll(nestedScrollConnection),
|
||||||
|
containerColor = Color(backgroundColor.toULong()),
|
||||||
|
topBar = {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = state.showMenu,
|
||||||
|
enter = slideInVertically { -it },
|
||||||
|
exit = slideOutVertically { -it }
|
||||||
|
) {
|
||||||
|
ReaderTopBar(
|
||||||
|
viewModel = viewModel,
|
||||||
|
navigator = navigator,
|
||||||
|
containerColor = systemBarsColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = state.showMenu,
|
||||||
|
enter = slideInVertically { it },
|
||||||
|
exit = slideOutVertically { it }
|
||||||
|
) {
|
||||||
|
ReaderBottomBar(
|
||||||
|
viewModel = viewModel,
|
||||||
|
libraryViewModel = libraryViewModel,
|
||||||
|
navigator = navigator,
|
||||||
|
scrollState = listState,
|
||||||
|
systemBarsColor = systemBarsColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
SelectionContainer {
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.clickable(
|
||||||
|
interactionSource = null,
|
||||||
|
indication = null,
|
||||||
|
enabled = !loading,
|
||||||
|
onClick = {
|
||||||
|
viewModel.onEvent(
|
||||||
|
ReaderEvent.OnShowHideMenu(context = context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (state.book.text.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
DisableSelection {
|
||||||
|
ReaderStartItem(viewModel = viewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsIndexed(
|
||||||
|
state.book.text, key = { _, key -> key.id }
|
||||||
|
) { index, text ->
|
||||||
|
if (index == 0)
|
||||||
|
Spacer(modifier = Modifier.height(36.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "${if (paragraphIndentation) " " else ""}${text.line}",
|
||||||
|
color = Color(fontColor.toULong()),
|
||||||
|
|
||||||
|
style = TextStyle(
|
||||||
|
lineBreak = LineBreak.Paragraph
|
||||||
|
),
|
||||||
|
fontFamily = fontFamily.font,
|
||||||
|
fontStyle = fontStyle,
|
||||||
|
fontSize = fontSize.sp,
|
||||||
|
lineHeight = (fontSize + lineHeight).sp,
|
||||||
|
|
||||||
|
modifier = Modifier.padding(horizontal = 18.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (index == state.book.text.lastIndex)
|
||||||
|
Spacer(modifier = Modifier.height(36.dp))
|
||||||
|
else
|
||||||
|
Spacer(modifier = Modifier.height((paragraphHeight * 3).dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.book.text.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
DisableSelection {
|
||||||
|
ReaderEndItem(
|
||||||
|
libraryViewModel = libraryViewModel,
|
||||||
|
viewModel = viewModel,
|
||||||
|
navigator = navigator
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading || state.errorMessage != null) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.background),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
if (!loading && state.errorMessage != null) {
|
||||||
|
IsError(
|
||||||
|
errorMessage = state.errorMessage!!.asString(),
|
||||||
|
icon = painterResource(id = R.drawable.error),
|
||||||
|
actionTitle = stringResource(id = R.string.go_back),
|
||||||
|
action = {
|
||||||
|
navigator.navigateBack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.loading),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier.fillMaxWidth(0.55f),
|
||||||
|
strokeCap = StrokeCap.Round,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
backgroundColor = MaterialTheme.elevation(2.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler {
|
||||||
|
navigator.navigateBack()
|
||||||
|
viewModel.onEvent(ReaderEvent.OnShowSystemBars(context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.reader.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.SliderDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.acclorite.books_history.presentation.Navigator
|
||||||
|
import com.acclorite.books_history.presentation.data.removeDigits
|
||||||
|
import com.acclorite.books_history.presentation.data.removeTrailingZero
|
||||||
|
import com.acclorite.books_history.presentation.screens.library.data.LibraryEvent
|
||||||
|
import com.acclorite.books_history.presentation.screens.library.data.LibraryViewModel
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderEvent
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReaderBottomBar(
|
||||||
|
viewModel: ReaderViewModel,
|
||||||
|
libraryViewModel: LibraryViewModel,
|
||||||
|
navigator: Navigator,
|
||||||
|
scrollState: LazyListState,
|
||||||
|
systemBarsColor: Color
|
||||||
|
) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
val book = state.book
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(systemBarsColor)
|
||||||
|
.clickable(
|
||||||
|
interactionSource = null,
|
||||||
|
indication = null,
|
||||||
|
onClick = {}
|
||||||
|
)
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
|
.padding(top = 16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text =
|
||||||
|
(book.progress * 100)
|
||||||
|
.toDouble()
|
||||||
|
.removeDigits(4)
|
||||||
|
.removeTrailingZero()
|
||||||
|
.dropWhile { it == '-' } + "%",
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
Slider(
|
||||||
|
value = book.progress,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.onEvent(
|
||||||
|
ReaderEvent.OnChangeProgress(
|
||||||
|
progress = it,
|
||||||
|
navigator = navigator,
|
||||||
|
refreshList = { book ->
|
||||||
|
libraryViewModel.onEvent(
|
||||||
|
LibraryEvent.OnUpdateBook(
|
||||||
|
book
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
viewModel.onEvent(ReaderEvent.OnScroll(scrollState, it))
|
||||||
|
},
|
||||||
|
colors = SliderDefaults.colors(
|
||||||
|
activeTrackColor = MaterialTheme.colorScheme.primary,
|
||||||
|
thumbColor = MaterialTheme.colorScheme.primary,
|
||||||
|
inactiveTrackColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(0.15f),
|
||||||
|
disabledActiveTickColor = Color.Transparent,
|
||||||
|
disabledActiveTrackColor = Color.Transparent,
|
||||||
|
disabledInactiveTickColor = Color.Transparent,
|
||||||
|
disabledInactiveTrackColor = Color.Transparent,
|
||||||
|
disabledThumbColor = Color.Transparent,
|
||||||
|
activeTickColor = Color.Transparent,
|
||||||
|
inactiveTickColor = Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.reader.components
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
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.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.acclorite.books_history.R
|
||||||
|
import com.acclorite.books_history.domain.model.Category
|
||||||
|
import com.acclorite.books_history.presentation.Navigator
|
||||||
|
import com.acclorite.books_history.presentation.Screen
|
||||||
|
import com.acclorite.books_history.presentation.screens.library.data.LibraryEvent
|
||||||
|
import com.acclorite.books_history.presentation.screens.library.data.LibraryViewModel
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderEvent
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReaderEndItem(
|
||||||
|
libraryViewModel: LibraryViewModel,
|
||||||
|
viewModel: ReaderViewModel,
|
||||||
|
navigator: Navigator
|
||||||
|
) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
val context = LocalContext.current as ComponentActivity
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
|
.padding(24.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(id = R.string.thanks_for_reading, state.book.title),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
if (state.book.category != Category.ALREADY_READ) {
|
||||||
|
viewModel.onEvent(
|
||||||
|
ReaderEvent.OnMoveBookToAlreadyRead(
|
||||||
|
refreshList = {
|
||||||
|
libraryViewModel.onEvent(
|
||||||
|
LibraryEvent.OnLoadList
|
||||||
|
)
|
||||||
|
},
|
||||||
|
updatePage = {
|
||||||
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(it))
|
||||||
|
},
|
||||||
|
navigator = navigator,
|
||||||
|
context = context
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.book_moved),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
} else {
|
||||||
|
viewModel.onEvent(
|
||||||
|
ReaderEvent.OnShowSystemBars(
|
||||||
|
context
|
||||||
|
)
|
||||||
|
)
|
||||||
|
navigator.navigate(Screen.LIBRARY)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentPadding = PaddingValues(horizontal = 12.dp),
|
||||||
|
colors = ButtonDefaults.textButtonColors(
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
contentColor = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
if (state.book.category != Category.ALREADY_READ) R.string.move_to_read
|
||||||
|
else R.string.back_to_library
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,300 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.reader.components
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
|
import androidx.compose.material3.BottomSheetDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.acclorite.books_history.R
|
||||||
|
import com.acclorite.books_history.domain.model.ChipItem
|
||||||
|
import com.acclorite.books_history.presentation.components.CategoryTitle
|
||||||
|
import com.acclorite.books_history.presentation.data.MainEvent
|
||||||
|
import com.acclorite.books_history.presentation.data.MainViewModel
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderEvent
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderViewModel
|
||||||
|
import com.acclorite.books_history.presentation.screens.settings.components.CheckboxWithTitle
|
||||||
|
import com.acclorite.books_history.presentation.screens.settings.components.ChipsWithTitle
|
||||||
|
import com.acclorite.books_history.presentation.screens.settings.components.ColorPickerWithTitle
|
||||||
|
import com.acclorite.books_history.presentation.screens.settings.components.SliderWithTitle
|
||||||
|
import com.acclorite.books_history.ui.ElevationDefaults
|
||||||
|
import com.acclorite.books_history.ui.elevation
|
||||||
|
import com.acclorite.books_history.util.Constants
|
||||||
|
|
||||||
|
@OptIn(
|
||||||
|
ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
|
||||||
|
ExperimentalLayoutApi::class
|
||||||
|
)
|
||||||
|
@Composable
|
||||||
|
fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderViewModel) {
|
||||||
|
val pagerState = rememberPagerState { 2 }
|
||||||
|
val currentPage by remember { derivedStateOf { pagerState.currentPage } }
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val navigationBarPadding =
|
||||||
|
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
||||||
|
|
||||||
|
val fontFamily = Constants.FONTS.find {
|
||||||
|
it.id == mainViewModel.fontFamily.collectAsState().value!!
|
||||||
|
} ?: Constants.FONTS[0]
|
||||||
|
val fontStyle = mainViewModel.isItalic.collectAsState().value!!
|
||||||
|
val fontSize = mainViewModel.fontSize.collectAsState().value!!
|
||||||
|
val lineHeight = mainViewModel.lineHeight.collectAsState().value!!
|
||||||
|
val paragraphHeight = mainViewModel.paragraphHeight.collectAsState().value!!
|
||||||
|
val paragraphIndentation = mainViewModel.paragraphIndentation.collectAsState().value!!
|
||||||
|
val backgroundColor = mainViewModel.backgroundColor.collectAsState().value!!
|
||||||
|
val fontColor = mainViewModel.fontColor.collectAsState().value!!
|
||||||
|
|
||||||
|
val scrimColor =
|
||||||
|
if (currentPage == 1) Color.Transparent else BottomSheetDefaults.ScrimColor
|
||||||
|
val animatedScrimColor by animateColorAsState(
|
||||||
|
targetValue = scrimColor,
|
||||||
|
animationSpec = tween(300),
|
||||||
|
label = "Scrim animation"
|
||||||
|
)
|
||||||
|
|
||||||
|
val height = if (currentPage == 1) 0.5f else 0.7f
|
||||||
|
val animatedHeight by animateFloatAsState(
|
||||||
|
targetValue = height,
|
||||||
|
animationSpec = tween(300),
|
||||||
|
label = "Height animation"
|
||||||
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(currentPage) {
|
||||||
|
viewModel.onEvent(
|
||||||
|
ReaderEvent.OnShowHideMenu(
|
||||||
|
currentPage != 1,
|
||||||
|
context as ComponentActivity
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ModalBottomSheet(
|
||||||
|
scrimColor = animatedScrimColor,
|
||||||
|
dragHandle = {},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight(animatedHeight),
|
||||||
|
onDismissRequest = {
|
||||||
|
viewModel.onEvent(ReaderEvent.OnShowHideSettingsBottomSheet)
|
||||||
|
},
|
||||||
|
sheetState = rememberModalBottomSheetState(true),
|
||||||
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
|
containerColor = MaterialTheme.elevation(ElevationDefaults.BottomSheet)
|
||||||
|
) {
|
||||||
|
ReaderSettingsBottomSheetTabRow(viewModel = viewModel, pagerState = pagerState)
|
||||||
|
|
||||||
|
HorizontalPager(state = pagerState, modifier = Modifier.fillMaxSize()) { page ->
|
||||||
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
|
if (page == 0) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(22.dp))
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
CategoryTitle(
|
||||||
|
title = stringResource(id = R.string.font_settings),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ChipsWithTitle(
|
||||||
|
title = stringResource(id = R.string.font_family_option),
|
||||||
|
chips = Constants.FONTS
|
||||||
|
.map {
|
||||||
|
ChipItem(
|
||||||
|
it.id,
|
||||||
|
it.fontName.asString(),
|
||||||
|
MaterialTheme.typography.labelLarge.copy(
|
||||||
|
fontFamily = it.font
|
||||||
|
),
|
||||||
|
it.id == fontFamily.id
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
mainViewModel.onEvent(MainEvent.OnChangeFontFamily(it.id))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ChipsWithTitle(
|
||||||
|
title = stringResource(id = R.string.font_style_option),
|
||||||
|
chips = listOf(
|
||||||
|
ChipItem(
|
||||||
|
"normal",
|
||||||
|
stringResource(id = R.string.font_style_normal),
|
||||||
|
MaterialTheme.typography.labelLarge.copy(
|
||||||
|
fontFamily = fontFamily.font,
|
||||||
|
fontStyle = FontStyle.Normal
|
||||||
|
),
|
||||||
|
!fontStyle
|
||||||
|
),
|
||||||
|
ChipItem(
|
||||||
|
"italic",
|
||||||
|
stringResource(id = R.string.font_style_italic),
|
||||||
|
MaterialTheme.typography.labelLarge.copy(
|
||||||
|
fontFamily = fontFamily.font,
|
||||||
|
fontStyle = FontStyle.Italic
|
||||||
|
),
|
||||||
|
fontStyle
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onClick = {
|
||||||
|
mainViewModel.onEvent(
|
||||||
|
MainEvent.OnChangeFontStyle(
|
||||||
|
when (it.id) {
|
||||||
|
"italic" -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SliderWithTitle(
|
||||||
|
value = fontSize to "pt",
|
||||||
|
fromValue = 10,
|
||||||
|
toValue = 35,
|
||||||
|
title = stringResource(id = R.string.font_size_option),
|
||||||
|
onValueChange = {
|
||||||
|
mainViewModel.onEvent(
|
||||||
|
MainEvent.OnChangeFontSize(it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(22.dp))
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
CategoryTitle(
|
||||||
|
title = stringResource(id = R.string.text_settings),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SliderWithTitle(
|
||||||
|
value = lineHeight to "pt",
|
||||||
|
fromValue = 1,
|
||||||
|
toValue = 16,
|
||||||
|
title = stringResource(id = R.string.line_height_option),
|
||||||
|
onValueChange = {
|
||||||
|
mainViewModel.onEvent(
|
||||||
|
MainEvent.OnChangeLineHeight(it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
SliderWithTitle(
|
||||||
|
value = paragraphHeight to "pt",
|
||||||
|
fromValue = 0,
|
||||||
|
toValue = 24,
|
||||||
|
title = stringResource(id = R.string.paragraph_height_option),
|
||||||
|
onValueChange = {
|
||||||
|
mainViewModel.onEvent(
|
||||||
|
MainEvent.OnChangeParagraphHeight(it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
CheckboxWithTitle(
|
||||||
|
selected = paragraphIndentation,
|
||||||
|
title = stringResource(id = R.string.paragraph_indentation_option)
|
||||||
|
) {
|
||||||
|
mainViewModel.onEvent(
|
||||||
|
MainEvent.OnChangeParagraphIndentation(!paragraphIndentation)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page == 1) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(14.dp))
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ColorPickerWithTitle(
|
||||||
|
value = Color(backgroundColor.toULong()),
|
||||||
|
title = stringResource(id = R.string.background_color_option),
|
||||||
|
onValueChange = {
|
||||||
|
mainViewModel.onEvent(
|
||||||
|
MainEvent.OnChangeBackgroundColor(
|
||||||
|
it.value.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ColorPickerWithTitle(
|
||||||
|
value = Color(fontColor.toULong()),
|
||||||
|
title = stringResource(id = R.string.font_color_option),
|
||||||
|
onValueChange = {
|
||||||
|
mainViewModel.onEvent(
|
||||||
|
MainEvent.OnChangeFontColor(
|
||||||
|
it.value.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.height(
|
||||||
|
8.dp + navigationBarPadding
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.reader.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.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.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReaderSettingsBottomSheetItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
verticalPadding: Dp = 8.dp,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
.padding(horizontal = 18.dp, vertical = verticalPadding),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
Text(
|
||||||
|
description,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.reader.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.pager.PagerState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.TabRow
|
||||||
|
import androidx.compose.material3.TabRowDefaults
|
||||||
|
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.acclorite.books_history.R
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderEvent
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun ReaderSettingsBottomSheetTabRow(viewModel: ReaderViewModel, pagerState: PagerState) {
|
||||||
|
val tabItems = listOf(
|
||||||
|
stringResource(id = R.string.general_tab),
|
||||||
|
stringResource(id = R.string.color_tab)
|
||||||
|
)
|
||||||
|
|
||||||
|
TabRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
selectedTabIndex = pagerState.currentPage,
|
||||||
|
divider = {
|
||||||
|
Divider(color = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
},
|
||||||
|
indicator = { tabPositions ->
|
||||||
|
TabRowDefaults.Indicator(
|
||||||
|
Modifier
|
||||||
|
.tabIndicatorOffset(tabPositions[pagerState.currentPage])
|
||||||
|
.padding(horizontal = 64.dp)
|
||||||
|
.clip(
|
||||||
|
RoundedCornerShape(
|
||||||
|
topStartPercent = 100,
|
||||||
|
topEndPercent = 100
|
||||||
|
)
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
tabItems.forEachIndexed { index, tabItem ->
|
||||||
|
Tab(
|
||||||
|
modifier = Modifier,
|
||||||
|
selected = pagerState.currentPage == index,
|
||||||
|
onClick = {
|
||||||
|
viewModel.onEvent(ReaderEvent.OnScrollToSettingsPage(index, pagerState))
|
||||||
|
},
|
||||||
|
selectedContentColor = MaterialTheme.colorScheme.primary,
|
||||||
|
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
tabItem,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.reader.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
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.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Image
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.acclorite.books_history.R
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderViewModel
|
||||||
|
import com.acclorite.books_history.ui.elevation
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReaderStartItem(viewModel: ReaderViewModel) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
|
.padding(24.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(135.dp)
|
||||||
|
.width(90.dp)
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.background(MaterialTheme.elevation())
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Image,
|
||||||
|
contentDescription = "Cover Image not found",
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.fillMaxWidth(0.7f)
|
||||||
|
.aspectRatio(1f),
|
||||||
|
tint = MaterialTheme.elevation(12.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (state.book.coverImage != null) {
|
||||||
|
Image(
|
||||||
|
bitmap = state.book.coverImage!!.asImageBitmap(),
|
||||||
|
contentDescription = "Cover",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.clip(MaterialTheme.shapes.medium),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(18.dp))
|
||||||
|
|
||||||
|
Column(verticalArrangement = Arrangement.Center) {
|
||||||
|
Text(
|
||||||
|
state.book.title,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
maxLines = 4,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
stringResource(id = R.string.happy_reading),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.reader.components
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.foundation.basicMarquee
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.acclorite.books_history.R
|
||||||
|
import com.acclorite.books_history.presentation.Argument
|
||||||
|
import com.acclorite.books_history.presentation.Navigator
|
||||||
|
import com.acclorite.books_history.presentation.Screen
|
||||||
|
import com.acclorite.books_history.presentation.data.removeDigits
|
||||||
|
import com.acclorite.books_history.presentation.data.removeTrailingZero
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderEvent
|
||||||
|
import com.acclorite.books_history.presentation.screens.reader.data.ReaderViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ReaderTopBar(viewModel: ReaderViewModel, navigator: Navigator, containerColor: Color) {
|
||||||
|
val context = LocalContext.current as ComponentActivity
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
val book = state.book
|
||||||
|
|
||||||
|
TopAppBar(
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
navigator.navigateBack()
|
||||||
|
viewModel.onEvent(ReaderEvent.OnShowSystemBars(context))
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = "Go back",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Column(verticalArrangement = Arrangement.Center) {
|
||||||
|
Text(
|
||||||
|
book.title,
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 20.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null,
|
||||||
|
onClick = {
|
||||||
|
navigator.navigateWithoutBackStack(
|
||||||
|
Screen.BOOK_INFO,
|
||||||
|
Argument(
|
||||||
|
"book",
|
||||||
|
book
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.basicMarquee(
|
||||||
|
delayMillis = 4000
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
id = R.string.read_query,
|
||||||
|
(book.progress * 100)
|
||||||
|
.toDouble()
|
||||||
|
.removeDigits(2)
|
||||||
|
.removeTrailingZero()
|
||||||
|
.dropWhile { it == '-' } + "%"
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
viewModel.onEvent(ReaderEvent.OnShowHideSettingsBottomSheet)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Settings,
|
||||||
|
contentDescription = "Open Settings",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = containerColor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
@file:OptIn(ExperimentalFoundationApi::class)
|
||||||
|
|
||||||
|
package com.acclorite.books_history.presentation.screens.reader.data
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.pager.PagerState
|
||||||
|
import com.acclorite.books_history.domain.model.Book
|
||||||
|
import com.acclorite.books_history.presentation.Navigator
|
||||||
|
|
||||||
|
sealed class ReaderEvent {
|
||||||
|
data class OnFileNotFound(val onLoaded: () -> Unit) : ReaderEvent()
|
||||||
|
data class OnLoadText(
|
||||||
|
val scrollState: LazyListState,
|
||||||
|
val navigator: Navigator,
|
||||||
|
val refreshList: (Book) -> Unit,
|
||||||
|
val onLoaded: () -> Unit
|
||||||
|
) : ReaderEvent()
|
||||||
|
|
||||||
|
data class OnShowHideMenu(val show: Boolean? = null, val context: ComponentActivity) :
|
||||||
|
ReaderEvent()
|
||||||
|
|
||||||
|
data class OnShowSystemBars(val context: ComponentActivity) : ReaderEvent()
|
||||||
|
data class OnChangeProgress(
|
||||||
|
val progress: Float,
|
||||||
|
val navigator: Navigator,
|
||||||
|
val refreshList: (Book) -> Unit
|
||||||
|
) : ReaderEvent()
|
||||||
|
|
||||||
|
data class OnScroll(val scrollState: LazyListState, val progress: Float) : ReaderEvent()
|
||||||
|
data object OnShowHideSettingsBottomSheet : ReaderEvent()
|
||||||
|
data class OnScrollToSettingsPage(val page: Int, val pagerState: PagerState) : ReaderEvent()
|
||||||
|
data class OnMoveBookToAlreadyRead(
|
||||||
|
val context: ComponentActivity,
|
||||||
|
val refreshList: () -> Unit,
|
||||||
|
val updatePage: (Int) -> Unit,
|
||||||
|
val navigator: Navigator
|
||||||
|
) : ReaderEvent()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.reader.data
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.acclorite.books_history.domain.model.Book
|
||||||
|
import com.acclorite.books_history.util.UIText
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class ReaderState(
|
||||||
|
val book: Book,
|
||||||
|
val errorMessage: UIText? = null,
|
||||||
|
|
||||||
|
val showMenu: Boolean = false,
|
||||||
|
|
||||||
|
val showSettingsBottomSheet: Boolean = false,
|
||||||
|
val currentPage: Int = 0,
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,293 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.reader.data
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.acclorite.books_history.R
|
||||||
|
import com.acclorite.books_history.domain.model.Book
|
||||||
|
import com.acclorite.books_history.domain.model.Category
|
||||||
|
import com.acclorite.books_history.domain.use_case.GetText
|
||||||
|
import com.acclorite.books_history.domain.use_case.UpdateBooks
|
||||||
|
import com.acclorite.books_history.presentation.Argument
|
||||||
|
import com.acclorite.books_history.presentation.Navigator
|
||||||
|
import com.acclorite.books_history.presentation.Screen
|
||||||
|
import com.acclorite.books_history.util.Resource
|
||||||
|
import com.acclorite.books_history.util.UIText
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.Date
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@HiltViewModel(assistedFactory = ReaderViewModel.Factory::class)
|
||||||
|
class ReaderViewModel @AssistedInject constructor(
|
||||||
|
@Assisted book: Book,
|
||||||
|
private val getText: GetText,
|
||||||
|
private val updateBooks: UpdateBooks
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _state = MutableStateFlow(ReaderState(book))
|
||||||
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
|
fun onEvent(event: ReaderEvent) {
|
||||||
|
when (event) {
|
||||||
|
is ReaderEvent.OnFileNotFound -> {
|
||||||
|
_state.update {
|
||||||
|
event.onLoaded()
|
||||||
|
it.copy(
|
||||||
|
errorMessage = UIText.StringResource(R.string.error_file_not_found),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is ReaderEvent.OnLoadText -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
if (_state.value.book.file == null) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_state.value.book.text.isEmpty()) {
|
||||||
|
getText.execute(_state.value.book.file!!).collect { result ->
|
||||||
|
when (result) {
|
||||||
|
is Resource.Loading -> Unit
|
||||||
|
is Resource.Success -> {
|
||||||
|
if (result.data?.isEmpty() != false) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
errorMessage = UIText.StringResource(
|
||||||
|
R.string.error_something_went_wrong
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
event.onLoaded()
|
||||||
|
return@collect
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
book = it.book.copy(
|
||||||
|
text = result.data,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
is Resource.Error -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
book = it.book.copy(
|
||||||
|
lastOpened = Date().time
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBooks.execute(
|
||||||
|
listOf(_state.value.book)
|
||||||
|
)
|
||||||
|
event.refreshList(_state.value.book)
|
||||||
|
event.navigator.putArgument(
|
||||||
|
Argument("book", _state.value.book)
|
||||||
|
)
|
||||||
|
//todo add history
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
snapshotFlow {
|
||||||
|
event.scrollState.layoutInfo.totalItemsCount
|
||||||
|
}.collectLatest { itemsCount ->
|
||||||
|
val scrollTo = (itemsCount * _state.value.book.progress).roundToInt()
|
||||||
|
if (itemsCount >= _state.value.book.text.size) {
|
||||||
|
if (scrollTo > 0) {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
event.scrollState.scrollToItem(scrollTo)
|
||||||
|
break
|
||||||
|
} catch (e: Exception) {
|
||||||
|
delay(50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//todo fix blinking
|
||||||
|
delay(500)
|
||||||
|
event.onLoaded()
|
||||||
|
return@collectLatest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is ReaderEvent.OnShowHideMenu -> {
|
||||||
|
val shouldShow = event.show ?: !_state.value.showMenu
|
||||||
|
val insetsController = WindowCompat.getInsetsController(
|
||||||
|
event.context.window,
|
||||||
|
event.context.window.decorView
|
||||||
|
)
|
||||||
|
|
||||||
|
insetsController.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
|
||||||
|
insetsController.apply {
|
||||||
|
if (shouldShow) {
|
||||||
|
show(WindowInsetsCompat.Type.systemBars())
|
||||||
|
} else {
|
||||||
|
hide(WindowInsetsCompat.Type.systemBars())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
showMenu = shouldShow
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is ReaderEvent.OnShowSystemBars -> {
|
||||||
|
val insetsController = WindowCompat.getInsetsController(
|
||||||
|
event.context.window,
|
||||||
|
event.context.window.decorView
|
||||||
|
)
|
||||||
|
|
||||||
|
insetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||||
|
}
|
||||||
|
|
||||||
|
is ReaderEvent.OnScroll -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val scrollTo = (_state.value.book.text.size * event.progress).roundToInt()
|
||||||
|
|
||||||
|
event.scrollState.scrollToItem(
|
||||||
|
scrollTo
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is ReaderEvent.OnChangeProgress -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
book = it.book.copy(
|
||||||
|
progress = event.progress
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBooks.execute(
|
||||||
|
listOf(_state.value.book)
|
||||||
|
)
|
||||||
|
event.navigator.putArgument(
|
||||||
|
Argument("book", _state.value.book)
|
||||||
|
)
|
||||||
|
event.refreshList(_state.value.book)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is ReaderEvent.OnShowHideSettingsBottomSheet -> {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
currentPage = if (it.showSettingsBottomSheet) it.currentPage else 0,
|
||||||
|
showSettingsBottomSheet = !it.showSettingsBottomSheet
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is ReaderEvent.OnScrollToSettingsPage -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
event.pagerState.scrollToPage(event.page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is ReaderEvent.OnMoveBookToAlreadyRead -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
onEvent(ReaderEvent.OnShowSystemBars(event.context))
|
||||||
|
|
||||||
|
val book = _state.value.book.copy(
|
||||||
|
category = Category.ALREADY_READ
|
||||||
|
)
|
||||||
|
updateBooks.execute(listOf(book))
|
||||||
|
|
||||||
|
event.refreshList()
|
||||||
|
|
||||||
|
event.updatePage(
|
||||||
|
Category.entries.dropLastWhile {
|
||||||
|
it != Category.ALREADY_READ
|
||||||
|
}.size - 1
|
||||||
|
)
|
||||||
|
event.navigator.navigate(Screen.LIBRARY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(
|
||||||
|
navigator: Navigator,
|
||||||
|
context: ComponentActivity,
|
||||||
|
scrollState: LazyListState,
|
||||||
|
refreshList: (Book) -> Unit,
|
||||||
|
onLoaded: () -> Unit
|
||||||
|
) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val book = navigator.retrieveArgument("book") as? Book
|
||||||
|
|
||||||
|
if (book == null) {
|
||||||
|
navigator.navigateBack()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
ReaderState(book = book)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (book.file == null) {
|
||||||
|
onEvent(ReaderEvent.OnFileNotFound(onLoaded = { onLoaded() }))
|
||||||
|
} else {
|
||||||
|
onEvent(ReaderEvent.OnShowHideMenu(false, context))
|
||||||
|
onEvent(
|
||||||
|
ReaderEvent.OnLoadText(
|
||||||
|
scrollState,
|
||||||
|
navigator,
|
||||||
|
refreshList = { refreshList(it) },
|
||||||
|
onLoaded = {
|
||||||
|
onLoaded()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(book: Book): ReaderViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
package com.acclorite.books_history.presentation.screens.settings
|
package com.acclorite.books_history.presentation.screens.settings
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.DisplaySettings
|
import androidx.compose.material.icons.filled.DisplaySettings
|
||||||
|
|
@ -28,14 +26,12 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import com.acclorite.books_history.R
|
import com.acclorite.books_history.R
|
||||||
import com.acclorite.books_history.presentation.Navigator
|
import com.acclorite.books_history.presentation.Navigator
|
||||||
import com.acclorite.books_history.presentation.Screen
|
import com.acclorite.books_history.presentation.Screen
|
||||||
import com.acclorite.books_history.presentation.main.MainViewModel
|
|
||||||
import com.acclorite.books_history.presentation.screens.settings.components.SettingsCategoryItem
|
import com.acclorite.books_history.presentation.screens.settings.components.SettingsCategoryItem
|
||||||
import com.acclorite.books_history.ui.elevation
|
import com.acclorite.books_history.ui.elevation
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(
|
fun SettingsScreen(
|
||||||
viewModel: MainViewModel,
|
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||||
|
|
@ -74,26 +70,32 @@ fun SettingsScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
LazyColumn(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = paddingValues.calculateTopPadding())
|
.padding(top = paddingValues.calculateTopPadding())
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
) {
|
||||||
SettingsCategoryItem(
|
item {
|
||||||
icon = Icons.Default.DisplaySettings,
|
SettingsCategoryItem(
|
||||||
text = stringResource(id = R.string.general_settings),
|
icon = Icons.Default.DisplaySettings,
|
||||||
description = stringResource(id = R.string.general_settings_desc)
|
text = stringResource(id = R.string.general_settings),
|
||||||
) {
|
description = stringResource(id = R.string.general_settings_desc)
|
||||||
navigator.navigate(Screen.GENERAL_SETTINGS)
|
) {
|
||||||
|
navigator.navigate(Screen.GENERAL_SETTINGS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SettingsCategoryItem(
|
|
||||||
icon = Icons.Default.Palette,
|
item {
|
||||||
text = stringResource(id = R.string.appearance_settings),
|
SettingsCategoryItem(
|
||||||
description = stringResource(id = R.string.appearance_settings_desc)
|
icon = Icons.Default.Palette,
|
||||||
) {
|
text = stringResource(id = R.string.appearance_settings),
|
||||||
navigator.navigate(Screen.APPEARANCE_SETTINGS)
|
description = stringResource(id = R.string.appearance_settings_desc)
|
||||||
|
) {
|
||||||
|
navigator.navigate(Screen.APPEARANCE_SETTINGS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//todo Reader settings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.settings.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.acclorite.books_history.presentation.components.CategoryTitle
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CheckboxWithTitle(
|
||||||
|
selected: Boolean,
|
||||||
|
title: String,
|
||||||
|
horizontalPadding: Dp = 18.dp,
|
||||||
|
verticalPadding: Dp = 12.dp,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = selected,
|
||||||
|
onCheckedChange = null,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(24.dp))
|
||||||
|
CategoryTitle(title = title, padding = 0.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.settings.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
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.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.ChipDefaults
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
import androidx.compose.material.FilterChip
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.acclorite.books_history.domain.model.ChipItem
|
||||||
|
import com.acclorite.books_history.presentation.components.CategoryTitle
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class, ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun ChipsWithTitle(
|
||||||
|
title: String,
|
||||||
|
chips: List<ChipItem>,
|
||||||
|
horizontalPadding: Dp = 18.dp,
|
||||||
|
verticalPadding: Dp = 8.dp,
|
||||||
|
onClick: (ChipItem) -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = horizontalPadding, vertical = verticalPadding)
|
||||||
|
) {
|
||||||
|
CategoryTitle(title = title, padding = 0.dp)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
FlowRow(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
content = {
|
||||||
|
chips.forEach { item ->
|
||||||
|
FilterChip(
|
||||||
|
modifier = Modifier.height(36.dp),
|
||||||
|
selected = item.selected,
|
||||||
|
enabled = !item.selected,
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
|
border = if (!item.selected) BorderStroke(
|
||||||
|
1.dp,
|
||||||
|
MaterialTheme.colorScheme.outline
|
||||||
|
) else null,
|
||||||
|
colors = ChipDefaults.filterChipColors(
|
||||||
|
backgroundColor = Color.Transparent,
|
||||||
|
disabledBackgroundColor = MaterialTheme.colorScheme.secondaryContainer
|
||||||
|
),
|
||||||
|
onClick = { onClick(item) },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
item.title,
|
||||||
|
style = item.textStyle,
|
||||||
|
color = if (item.selected) {
|
||||||
|
MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
},
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.settings.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
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.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.History
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.acclorite.books_history.R
|
||||||
|
import com.acclorite.books_history.presentation.components.CategoryTitle
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ColorPickerWithTitle(
|
||||||
|
value: Color,
|
||||||
|
title: String,
|
||||||
|
horizontalPadding: Dp = 18.dp,
|
||||||
|
verticalPadding: Dp = 8.dp,
|
||||||
|
onValueChange: (Color) -> Unit
|
||||||
|
) {
|
||||||
|
val initialValue = remember { value }
|
||||||
|
var color by remember { mutableStateOf(value) }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = verticalPadding, horizontal = horizontalPadding)
|
||||||
|
) {
|
||||||
|
CategoryTitle(
|
||||||
|
title = title,
|
||||||
|
padding = 0.dp,
|
||||||
|
// color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
RevertibleSlider(
|
||||||
|
value = color.red to "",
|
||||||
|
initialValue = initialValue.red,
|
||||||
|
title = stringResource(id = R.string.red_color),
|
||||||
|
onValueChange = {
|
||||||
|
color = color.copy(red = it)
|
||||||
|
onValueChange(color)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
RevertibleSlider(
|
||||||
|
value = color.green to "",
|
||||||
|
initialValue = initialValue.green,
|
||||||
|
title = stringResource(id = R.string.green_color),
|
||||||
|
onValueChange = {
|
||||||
|
color = color.copy(green = it)
|
||||||
|
onValueChange(color)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
RevertibleSlider(
|
||||||
|
value = color.blue to "",
|
||||||
|
initialValue = initialValue.blue,
|
||||||
|
title = stringResource(id = R.string.blue_color),
|
||||||
|
onValueChange = {
|
||||||
|
color = color.copy(blue = it)
|
||||||
|
onValueChange(color)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RevertibleSlider(
|
||||||
|
value: Pair<Float, String>,
|
||||||
|
initialValue: Float,
|
||||||
|
title: String,
|
||||||
|
horizontalPadding: Dp = 0.dp,
|
||||||
|
verticalPadding: Dp = 8.dp,
|
||||||
|
onValueChange: (Float) -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
horizontal = horizontalPadding,
|
||||||
|
vertical = verticalPadding
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
SliderWithTitle(
|
||||||
|
modifier = Modifier.weight(0.85f),
|
||||||
|
value = value,
|
||||||
|
title = title,
|
||||||
|
toValue = 255,
|
||||||
|
onValueChange = {
|
||||||
|
onValueChange(it)
|
||||||
|
},
|
||||||
|
horizontalPadding = 0.dp,
|
||||||
|
verticalPadding = 0.dp
|
||||||
|
)
|
||||||
|
Box(modifier = Modifier.weight(0.15f)) {
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.align(Alignment.CenterEnd),
|
||||||
|
enabled = initialValue != value.first,
|
||||||
|
onClick = { onValueChange(initialValue) }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.History,
|
||||||
|
contentDescription = "Revert to initial",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(28.dp),
|
||||||
|
tint = if (initialValue == value.first) MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
else MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
package com.acclorite.books_history.presentation.screens.settings.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
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.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.acclorite.books_history.presentation.components.CategoryTitle
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SliderWithTitle(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
value: Pair<Int, String>,
|
||||||
|
fromValue: Int,
|
||||||
|
toValue: Int,
|
||||||
|
title: String,
|
||||||
|
horizontalPadding: Dp = 18.dp,
|
||||||
|
verticalPadding: Dp = 8.dp,
|
||||||
|
onValueChange: (Int) -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(Modifier.fillMaxWidth(0.2f)) {
|
||||||
|
CategoryTitle(title = title, padding = 0.dp)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "${value.first}${value.second}",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Slider(
|
||||||
|
valueRange = fromValue.toFloat()..toValue.toFloat(),
|
||||||
|
value = value.first.toFloat(),
|
||||||
|
onValueChange = {
|
||||||
|
onValueChange(it.roundToInt())
|
||||||
|
},
|
||||||
|
steps = toValue - fromValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SliderWithTitle(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
value: Pair<Float, String>,
|
||||||
|
title: String,
|
||||||
|
toValue: Int,
|
||||||
|
horizontalPadding: Dp = 18.dp,
|
||||||
|
verticalPadding: Dp = 8.dp,
|
||||||
|
onValueChange: (Float) -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(Modifier.fillMaxWidth(0.2f)) {
|
||||||
|
CategoryTitle(title = title, padding = 0.dp)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "${(value.first * toValue).roundToInt()}${value.second}",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Slider(
|
||||||
|
valueRange = 0f..1f,
|
||||||
|
value = value.first,
|
||||||
|
onValueChange = {
|
||||||
|
onValueChange(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
package com.acclorite.books_history.presentation.screens.settings.nested.appearance
|
package com.acclorite.books_history.presentation.screens.settings.nested.appearance
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
|
@ -20,39 +18,30 @@ import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
|
||||||
import com.acclorite.books_history.R
|
import com.acclorite.books_history.R
|
||||||
|
import com.acclorite.books_history.domain.model.ChipItem
|
||||||
import com.acclorite.books_history.presentation.Navigator
|
import com.acclorite.books_history.presentation.Navigator
|
||||||
import com.acclorite.books_history.presentation.main.MainViewModel
|
import com.acclorite.books_history.presentation.data.MainEvent
|
||||||
import com.acclorite.books_history.presentation.screens.settings.components.SettingsOptionItem
|
import com.acclorite.books_history.presentation.data.MainViewModel
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.appearance.components.AppearanceSettingsDarkThemeDialog
|
import com.acclorite.books_history.presentation.screens.settings.components.ChipsWithTitle
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.appearance.components.AppearanceSettingsThemeSwitcher
|
import com.acclorite.books_history.presentation.screens.settings.nested.appearance.components.AppearanceSettingsThemeSwitcher
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.appearance.data.AppearanceSettingsEvent
|
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.appearance.data.AppearanceSettingsViewModel
|
|
||||||
import com.acclorite.books_history.ui.DarkTheme
|
import com.acclorite.books_history.ui.DarkTheme
|
||||||
import com.acclorite.books_history.ui.elevation
|
import com.acclorite.books_history.ui.elevation
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AppearanceSettings(
|
fun AppearanceSettings(
|
||||||
viewModel: AppearanceSettingsViewModel = hiltViewModel(),
|
|
||||||
mainViewModel: MainViewModel,
|
mainViewModel: MainViewModel,
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
|
|
||||||
val darkTheme = mainViewModel.darkTheme.collectAsState().value!!
|
val darkTheme = mainViewModel.darkTheme.collectAsState().value!!
|
||||||
|
|
||||||
if (state.showDarkThemeDialog) {
|
|
||||||
AppearanceSettingsDarkThemeDialog(viewModel = viewModel, mainViewModel = mainViewModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -87,23 +76,38 @@ fun AppearanceSettings(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
LazyColumn(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = paddingValues.calculateTopPadding())
|
.padding(top = paddingValues.calculateTopPadding())
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
) {
|
||||||
SettingsOptionItem(
|
item {
|
||||||
title = stringResource(id = R.string.dark_theme_option),
|
ChipsWithTitle(
|
||||||
description = when (darkTheme) {
|
title = stringResource(id = R.string.dark_theme_option),
|
||||||
DarkTheme.OFF -> stringResource(id = R.string.dark_theme_off)
|
chips = DarkTheme.entries.map {
|
||||||
DarkTheme.ON -> stringResource(id = R.string.dark_theme_on)
|
ChipItem(
|
||||||
DarkTheme.FOLLOW_SYSTEM -> stringResource(id = R.string.dark_theme_follow_system)
|
it.toString(),
|
||||||
|
when (it) {
|
||||||
|
DarkTheme.OFF -> stringResource(id = R.string.dark_theme_off)
|
||||||
|
DarkTheme.ON -> stringResource(id = R.string.dark_theme_on)
|
||||||
|
DarkTheme.FOLLOW_SYSTEM -> stringResource(id = R.string.dark_theme_follow_system)
|
||||||
|
},
|
||||||
|
MaterialTheme.typography.labelLarge,
|
||||||
|
it == darkTheme
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
mainViewModel.onEvent(
|
||||||
|
MainEvent.OnChangeDarkTheme(
|
||||||
|
it.id
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
viewModel.onEvent(AppearanceSettingsEvent.OnShowHideDarkThemeDialog)
|
|
||||||
}
|
}
|
||||||
AppearanceSettingsThemeSwitcher(mainViewModel = mainViewModel)
|
|
||||||
|
item {
|
||||||
|
AppearanceSettingsThemeSwitcher(mainViewModel = mainViewModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
package com.acclorite.books_history.presentation.screens.settings.nested.appearance.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.acclorite.books_history.R
|
|
||||||
import com.acclorite.books_history.presentation.components.CustomDialogWithLazyColumn
|
|
||||||
import com.acclorite.books_history.presentation.components.SelectableDialogItem
|
|
||||||
import com.acclorite.books_history.presentation.main.MainEvent
|
|
||||||
import com.acclorite.books_history.presentation.main.MainViewModel
|
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.appearance.data.AppearanceSettingsEvent
|
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.appearance.data.AppearanceSettingsViewModel
|
|
||||||
import com.acclorite.books_history.ui.DarkTheme
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AppearanceSettingsDarkThemeDialog(
|
|
||||||
viewModel: AppearanceSettingsViewModel,
|
|
||||||
mainViewModel: MainViewModel
|
|
||||||
) {
|
|
||||||
val darkTheme = mainViewModel.darkTheme.collectAsState().value!!
|
|
||||||
|
|
||||||
CustomDialogWithLazyColumn(
|
|
||||||
title = stringResource(id = R.string.dark_theme_option),
|
|
||||||
imageVectorIcon = null,
|
|
||||||
description = null,
|
|
||||||
actionText = null,
|
|
||||||
isActionEnabled = null,
|
|
||||||
onDismiss = { viewModel.onEvent(AppearanceSettingsEvent.OnShowHideDarkThemeDialog) },
|
|
||||||
onAction = {},
|
|
||||||
withDivider = false,
|
|
||||||
items = {
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
}
|
|
||||||
|
|
||||||
items(DarkTheme.entries) {
|
|
||||||
val theme = when (it) {
|
|
||||||
DarkTheme.OFF -> stringResource(id = R.string.dark_theme_off)
|
|
||||||
DarkTheme.ON -> stringResource(id = R.string.dark_theme_on)
|
|
||||||
DarkTheme.FOLLOW_SYSTEM -> stringResource(id = R.string.dark_theme_follow_system)
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectableDialogItem(selected = it == darkTheme, title = theme) {
|
|
||||||
mainViewModel.onEvent(
|
|
||||||
MainEvent.OnChangeDarkTheme(
|
|
||||||
it.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -5,21 +5,18 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.acclorite.books_history.R
|
import com.acclorite.books_history.R
|
||||||
import com.acclorite.books_history.presentation.main.MainEvent
|
import com.acclorite.books_history.presentation.components.CategoryTitle
|
||||||
import com.acclorite.books_history.presentation.main.MainViewModel
|
import com.acclorite.books_history.presentation.data.MainEvent
|
||||||
|
import com.acclorite.books_history.presentation.data.MainViewModel
|
||||||
import com.acclorite.books_history.ui.Theme
|
import com.acclorite.books_history.ui.Theme
|
||||||
import com.acclorite.books_history.ui.isDark
|
import com.acclorite.books_history.ui.isDark
|
||||||
import com.acclorite.books_history.util.Constants
|
import com.acclorite.books_history.util.Constants
|
||||||
|
|
@ -38,21 +35,18 @@ fun AppearanceSettingsThemeSwitcher(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.app_theme_option),
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
fontSize = 18.sp,
|
|
||||||
modifier = Modifier.padding(start = 16.dp, top = 8.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
CategoryTitle(
|
||||||
|
title = stringResource(id = R.string.app_theme_option)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
LazyRow(
|
LazyRow(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
itemsIndexed(themes) { index, themeEntry ->
|
itemsIndexed(themes) { index, themeEntry ->
|
||||||
if (index == 0)
|
if (index == 0)
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(18.dp))
|
||||||
|
|
||||||
AppearanceSettingsThemeSwitcherItem(
|
AppearanceSettingsThemeSwitcherItem(
|
||||||
theme = themeEntry,
|
theme = themeEntry,
|
||||||
|
|
@ -65,7 +59,7 @@ fun AppearanceSettingsThemeSwitcher(
|
||||||
if (index != Theme.entries.lastIndex) {
|
if (index != Theme.entries.lastIndex) {
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
} else {
|
} else {
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(18.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -133,8 +133,8 @@ fun AppearanceSettingsThemeSwitcherItem(
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
Text(
|
Text(
|
||||||
text = theme.second.asString(),
|
text = theme.second.asString(),
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package com.acclorite.books_history.presentation.screens.settings.nested.appearance.data
|
|
||||||
|
|
||||||
sealed class AppearanceSettingsEvent() {
|
|
||||||
data object OnShowHideDarkThemeDialog : AppearanceSettingsEvent()
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package com.acclorite.books_history.presentation.screens.settings.nested.appearance.data
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
data class AppearanceSettingsState(
|
|
||||||
val showDarkThemeDialog: Boolean = false
|
|
||||||
)
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package com.acclorite.books_history.presentation.screens.settings.nested.appearance.data
|
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@HiltViewModel
|
|
||||||
class AppearanceSettingsViewModel @Inject constructor(
|
|
||||||
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
private val _state = MutableStateFlow(AppearanceSettingsState())
|
|
||||||
val state = _state.asStateFlow()
|
|
||||||
|
|
||||||
fun onEvent(event: AppearanceSettingsEvent) {
|
|
||||||
when (event) {
|
|
||||||
is AppearanceSettingsEvent.OnShowHideDarkThemeDialog -> {
|
|
||||||
_state.update {
|
|
||||||
it.copy(
|
|
||||||
showDarkThemeDialog = !it.showDarkThemeDialog
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
package com.acclorite.books_history.presentation.screens.settings.nested.general
|
package com.acclorite.books_history.presentation.screens.settings.nested.general
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
|
@ -20,38 +19,31 @@ import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
|
||||||
import com.acclorite.books_history.R
|
import com.acclorite.books_history.R
|
||||||
|
import com.acclorite.books_history.domain.model.ChipItem
|
||||||
import com.acclorite.books_history.presentation.Navigator
|
import com.acclorite.books_history.presentation.Navigator
|
||||||
import com.acclorite.books_history.presentation.main.MainViewModel
|
import com.acclorite.books_history.presentation.data.MainEvent
|
||||||
import com.acclorite.books_history.presentation.screens.settings.components.SettingsOptionItem
|
import com.acclorite.books_history.presentation.data.MainViewModel
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.general.components.GeneralSettingsLanguageDialog
|
import com.acclorite.books_history.presentation.screens.settings.components.ChipsWithTitle
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.general.data.GeneralSettingsEvent
|
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.general.data.GeneralSettingsViewModel
|
|
||||||
import com.acclorite.books_history.ui.elevation
|
import com.acclorite.books_history.ui.elevation
|
||||||
import com.acclorite.books_history.util.Constants
|
import com.acclorite.books_history.util.Constants
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun GeneralSettings(
|
fun GeneralSettings(
|
||||||
viewModel: GeneralSettingsViewModel = hiltViewModel(),
|
|
||||||
mainViewModel: MainViewModel,
|
mainViewModel: MainViewModel,
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||||
val state by viewModel.state.collectAsState()
|
val context = LocalContext.current as ComponentActivity
|
||||||
|
|
||||||
val language = mainViewModel.language.collectAsState().value!!
|
val language = mainViewModel.language.collectAsState().value!!
|
||||||
|
|
||||||
if (state.showLanguageDialog) {
|
|
||||||
GeneralSettingsLanguageDialog(viewModel = viewModel, mainViewModel = mainViewModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -86,18 +78,30 @@ fun GeneralSettings(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
LazyColumn(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = paddingValues.calculateTopPadding())
|
.padding(top = paddingValues.calculateTopPadding())
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
) {
|
||||||
SettingsOptionItem(
|
item {
|
||||||
title = stringResource(id = R.string.language_option),
|
ChipsWithTitle(
|
||||||
description = Constants.LANGUAGES.find { it.first == language }?.second
|
title = stringResource(id = R.string.language_option),
|
||||||
?: return@Column
|
chips = Constants.LANGUAGES.map {
|
||||||
) {
|
ChipItem(
|
||||||
viewModel.onEvent(GeneralSettingsEvent.OnShowHideLanguageDialog)
|
it.first,
|
||||||
|
it.second,
|
||||||
|
MaterialTheme.typography.labelLarge,
|
||||||
|
it.first == language
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
mainViewModel.onEvent(
|
||||||
|
MainEvent.OnChangeLanguage(
|
||||||
|
it.id,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
package com.acclorite.books_history.presentation.screens.settings.nested.general.components
|
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.acclorite.books_history.R
|
|
||||||
import com.acclorite.books_history.presentation.components.CustomDialogWithLazyColumn
|
|
||||||
import com.acclorite.books_history.presentation.components.SelectableDialogItem
|
|
||||||
import com.acclorite.books_history.presentation.main.MainEvent
|
|
||||||
import com.acclorite.books_history.presentation.main.MainViewModel
|
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.general.data.GeneralSettingsEvent
|
|
||||||
import com.acclorite.books_history.presentation.screens.settings.nested.general.data.GeneralSettingsViewModel
|
|
||||||
import com.acclorite.books_history.util.Constants
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GeneralSettingsLanguageDialog(
|
|
||||||
viewModel: GeneralSettingsViewModel,
|
|
||||||
mainViewModel: MainViewModel
|
|
||||||
) {
|
|
||||||
val language = mainViewModel.language.collectAsState().value!!
|
|
||||||
val activity = LocalContext.current as ComponentActivity
|
|
||||||
|
|
||||||
CustomDialogWithLazyColumn(
|
|
||||||
title = stringResource(id = R.string.language_option),
|
|
||||||
imageVectorIcon = null,
|
|
||||||
description = null,
|
|
||||||
actionText = null,
|
|
||||||
isActionEnabled = null,
|
|
||||||
onDismiss = { viewModel.onEvent(GeneralSettingsEvent.OnShowHideLanguageDialog) },
|
|
||||||
onAction = {},
|
|
||||||
withDivider = false,
|
|
||||||
items = {
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
}
|
|
||||||
items(Constants.LANGUAGES) { lang ->
|
|
||||||
SelectableDialogItem(selected = lang.first == language, title = lang.second) {
|
|
||||||
mainViewModel.onEvent(
|
|
||||||
MainEvent.OnChangeLanguage(
|
|
||||||
lang.first,
|
|
||||||
activity
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package com.acclorite.books_history.presentation.screens.settings.nested.general.data
|
|
||||||
|
|
||||||
sealed class GeneralSettingsEvent() {
|
|
||||||
data object OnShowHideLanguageDialog : GeneralSettingsEvent()
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package com.acclorite.books_history.presentation.screens.settings.nested.general.data
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
data class GeneralSettingsState(
|
|
||||||
val showLanguageDialog: Boolean = false
|
|
||||||
)
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
package com.acclorite.books_history.presentation.screens.settings.nested.general.data
|
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@HiltViewModel
|
|
||||||
class GeneralSettingsViewModel @Inject constructor(
|
|
||||||
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
private val _state = MutableStateFlow(GeneralSettingsState())
|
|
||||||
val state = _state.asStateFlow()
|
|
||||||
|
|
||||||
fun onEvent(event: GeneralSettingsEvent) {
|
|
||||||
when (event) {
|
|
||||||
is GeneralSettingsEvent.OnShowHideLanguageDialog -> {
|
|
||||||
_state.update {
|
|
||||||
it.copy(
|
|
||||||
showLanguageDialog = !it.showLanguageDialog
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,6 +52,11 @@ fun BooksHistoryResurrectionTheme(
|
||||||
window.statusBarColor = Color.Transparent.toArgb()
|
window.statusBarColor = Color.Transparent.toArgb()
|
||||||
window.navigationBarColor = Color.Transparent.toArgb()
|
window.navigationBarColor = Color.Transparent.toArgb()
|
||||||
|
|
||||||
|
// Fix for nav bar being semi transparent in api 29+
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
window.isNavigationBarContrastEnforced = false
|
||||||
|
}
|
||||||
|
|
||||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !isDark
|
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !isDark
|
||||||
WindowCompat.getInsetsController(window, view).isAppearanceLightNavigationBars = !isDark
|
WindowCompat.getInsetsController(window, view).isAppearanceLightNavigationBars = !isDark
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ val Typography = Typography(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = FontFamily.Default,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
lineHeight = 24.sp
|
lineHeight = 20.sp
|
||||||
),
|
),
|
||||||
titleSmall = TextStyle(
|
titleSmall = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = FontFamily.Default,
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,13 @@ object Constants {
|
||||||
// Fonts for Reader
|
// Fonts for Reader
|
||||||
val FONTS = listOf(
|
val FONTS = listOf(
|
||||||
FontWithName(
|
FontWithName(
|
||||||
"Raleway",
|
"default",
|
||||||
|
UIText.StringResource(R.string.default_font),
|
||||||
|
FontFamily.Default
|
||||||
|
),
|
||||||
|
FontWithName(
|
||||||
|
"raleway",
|
||||||
|
UIText.StringValue("Raleway"),
|
||||||
FontFamily(
|
FontFamily(
|
||||||
Font(R.font.raleway_regular),
|
Font(R.font.raleway_regular),
|
||||||
Font(R.font.raleway_regular_italic, style = FontStyle.Italic),
|
Font(R.font.raleway_regular_italic, style = FontStyle.Italic),
|
||||||
|
|
@ -60,7 +66,8 @@ object Constants {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
FontWithName(
|
FontWithName(
|
||||||
"Open Sans",
|
"open_sans",
|
||||||
|
UIText.StringValue("Open Sans"),
|
||||||
FontFamily(
|
FontFamily(
|
||||||
Font(R.font.opensans_regular),
|
Font(R.font.opensans_regular),
|
||||||
Font(R.font.opensans_regular_italic, style = FontStyle.Italic),
|
Font(R.font.opensans_regular_italic, style = FontStyle.Italic),
|
||||||
|
|
@ -71,7 +78,8 @@ object Constants {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
FontWithName(
|
FontWithName(
|
||||||
"Mulish",
|
"mulish",
|
||||||
|
UIText.StringValue("Mulish"),
|
||||||
FontFamily(
|
FontFamily(
|
||||||
Font(R.font.mulish_regular),
|
Font(R.font.mulish_regular),
|
||||||
Font(R.font.mulish_regular_italic, style = FontStyle.Italic),
|
Font(R.font.mulish_regular_italic, style = FontStyle.Italic),
|
||||||
|
|
@ -82,7 +90,8 @@ object Constants {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
FontWithName(
|
FontWithName(
|
||||||
"Arimo",
|
"arimo",
|
||||||
|
UIText.StringValue("Arimo"),
|
||||||
FontFamily(
|
FontFamily(
|
||||||
Font(R.font.arimo_regular),
|
Font(R.font.arimo_regular),
|
||||||
Font(R.font.arimo_regular_italic, style = FontStyle.Italic),
|
Font(R.font.arimo_regular_italic, style = FontStyle.Italic),
|
||||||
|
|
@ -93,7 +102,8 @@ object Constants {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
FontWithName(
|
FontWithName(
|
||||||
"Garamond",
|
"garamond",
|
||||||
|
UIText.StringValue("Garamond"),
|
||||||
FontFamily(
|
FontFamily(
|
||||||
Font(R.font.garamond_regular),
|
Font(R.font.garamond_regular),
|
||||||
Font(R.font.garamond_regular_italic, style = FontStyle.Italic),
|
Font(R.font.garamond_regular_italic, style = FontStyle.Italic),
|
||||||
|
|
@ -104,7 +114,8 @@ object Constants {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
FontWithName(
|
FontWithName(
|
||||||
"Roboto Serif",
|
"roboto_serif",
|
||||||
|
UIText.StringValue("Roboto Serif"),
|
||||||
FontFamily(
|
FontFamily(
|
||||||
Font(R.font.robotoserif_regular),
|
Font(R.font.robotoserif_regular),
|
||||||
Font(R.font.robotoserif_regular_italic, style = FontStyle.Italic),
|
Font(R.font.robotoserif_regular_italic, style = FontStyle.Italic),
|
||||||
|
|
@ -115,7 +126,8 @@ object Constants {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
FontWithName(
|
FontWithName(
|
||||||
"Noto Serif",
|
"noto_serif",
|
||||||
|
UIText.StringValue("Noto Serif"),
|
||||||
FontFamily(
|
FontFamily(
|
||||||
Font(R.font.notoserif_regular),
|
Font(R.font.notoserif_regular),
|
||||||
Font(R.font.notoserif_regular_italic, style = FontStyle.Italic),
|
Font(R.font.notoserif_regular_italic, style = FontStyle.Italic),
|
||||||
|
|
@ -126,7 +138,8 @@ object Constants {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
FontWithName(
|
FontWithName(
|
||||||
"Noto Sans",
|
"noto_sans",
|
||||||
|
UIText.StringValue("Noto Sans"),
|
||||||
FontFamily(
|
FontFamily(
|
||||||
Font(R.font.notosans_regular),
|
Font(R.font.notosans_regular),
|
||||||
Font(R.font.notosans_regular_italic, style = FontStyle.Italic),
|
Font(R.font.notosans_regular_italic, style = FontStyle.Italic),
|
||||||
|
|
@ -137,7 +150,8 @@ object Constants {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
FontWithName(
|
FontWithName(
|
||||||
"Roboto",
|
"roboto",
|
||||||
|
UIText.StringValue("Roboto"),
|
||||||
FontFamily(
|
FontFamily(
|
||||||
Font(R.font.roboto_regular),
|
Font(R.font.roboto_regular),
|
||||||
Font(R.font.roboto_regular_italic, style = FontStyle.Italic),
|
Font(R.font.roboto_regular_italic, style = FontStyle.Italic),
|
||||||
|
|
@ -148,7 +162,8 @@ object Constants {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
FontWithName(
|
FontWithName(
|
||||||
"Jost",
|
"jost",
|
||||||
|
UIText.StringValue("Jost"),
|
||||||
FontFamily(
|
FontFamily(
|
||||||
Font(R.font.jost_regular),
|
Font(R.font.jost_regular),
|
||||||
Font(R.font.jost_regular_italic, style = FontStyle.Italic),
|
Font(R.font.jost_regular_italic, style = FontStyle.Italic),
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,12 @@
|
||||||
<string name="error_no_description">
|
<string name="error_no_description">
|
||||||
Немає опису.
|
Немає опису.
|
||||||
</string>
|
</string>
|
||||||
|
<string name="error_file_not_found">
|
||||||
|
Файл не знайдено. Будь ласка, перевірте чи файл цієї книги все ще існує.
|
||||||
|
</string>
|
||||||
|
<string name="error_something_went_wrong">
|
||||||
|
Щось пішло не так. Будь ласка, перевірте чи файл цієї книги не пошкоджений.
|
||||||
|
</string>
|
||||||
|
|
||||||
<!-- Is Empty messages -->
|
<!-- Is Empty messages -->
|
||||||
<string name="browse_empty">
|
<string name="browse_empty">
|
||||||
|
|
@ -64,6 +70,7 @@
|
||||||
<string name="selected_items_count_query">%1$s обрано</string>
|
<string name="selected_items_count_query">%1$s обрано</string>
|
||||||
<string name="search_query">Знайти %1$s…</string>
|
<string name="search_query">Знайти %1$s…</string>
|
||||||
<string name="you_read_query">Ви прочитали %1$s цієї книги.</string>
|
<string name="you_read_query">Ви прочитали %1$s цієї книги.</string>
|
||||||
|
<string name="read_query">%1$s прочитано</string>
|
||||||
|
|
||||||
<!-- Action Texts -->
|
<!-- Action Texts -->
|
||||||
<string name="cancel">Відмінити</string>
|
<string name="cancel">Відмінити</string>
|
||||||
|
|
@ -77,6 +84,9 @@
|
||||||
<string name="start">Почати</string>
|
<string name="start">Почати</string>
|
||||||
<string name="continue_read">Продовжити</string>
|
<string name="continue_read">Продовжити</string>
|
||||||
<string name="never">Ніколи</string>
|
<string name="never">Ніколи</string>
|
||||||
|
<string name="go_back">Назад</string>
|
||||||
|
<string name="move_to_read">Перемістити цю книгу в \"Прочитано\"</string>
|
||||||
|
<string name="back_to_library">Назад в Бібліотеку</string>
|
||||||
|
|
||||||
<!-- Items -->
|
<!-- Items -->
|
||||||
<string name="files">файли</string>
|
<string name="files">файли</string>
|
||||||
|
|
@ -96,23 +106,44 @@
|
||||||
<string name="already_read_tab">Прочитано</string>
|
<string name="already_read_tab">Прочитано</string>
|
||||||
<string name="planning_tab">Планую</string>
|
<string name="planning_tab">Планую</string>
|
||||||
<string name="dropped_tab">Кинуто</string>
|
<string name="dropped_tab">Кинуто</string>
|
||||||
|
<string name="general_tab">Головні</string>
|
||||||
|
<string name="color_tab">Кольори</string>
|
||||||
|
|
||||||
<!-- Settings categories -->
|
<!-- Settings categories -->
|
||||||
<string name="general_settings">Головні</string>
|
<string name="general_settings">Головні</string>
|
||||||
<string name="general_settings_desc">Мова застосунку</string>
|
<string name="general_settings_desc">Мова застосунку</string>
|
||||||
<string name="appearance_settings">Зовнішній вигляд</string>
|
<string name="appearance_settings">Зовнішній вигляд</string>
|
||||||
<string name="appearance_settings_desc">Тема застосунку, темна тема</string>
|
<string name="appearance_settings_desc">Тема застосунку, темна тема</string>
|
||||||
|
<string name="font_settings">Шрифт</string>
|
||||||
|
<string name="text_settings">Текст</string>
|
||||||
|
|
||||||
<!-- Settings options -->
|
<!-- Settings options -->
|
||||||
<string name="language_option">Мова застосунку</string>
|
<string name="language_option">Мова застосунку</string>
|
||||||
<string name="dark_theme_option">Темна тема</string>
|
<string name="dark_theme_option">Темна тема</string>
|
||||||
<string name="app_theme_option">Тема застосунку</string>
|
<string name="app_theme_option">Тема застосунку</string>
|
||||||
|
<string name="font_family_option">Сімейство шрифтів</string>
|
||||||
|
<string name="font_style_option">Стиль шрифту</string>
|
||||||
|
<string name="font_size_option">Розмір шрифту</string>
|
||||||
|
<string name="line_height_option">Висота рядка</string>
|
||||||
|
<string name="paragraph_height_option">Висота абзацу</string>
|
||||||
|
<string name="paragraph_indentation_option">Абзацний відступ</string>
|
||||||
|
<string name="background_color_option">Колір фону</string>
|
||||||
|
<string name="font_color_option">Колір шрифту</string>
|
||||||
|
|
||||||
|
<!-- Colors -->
|
||||||
|
<string name="red_color">Червоний</string>
|
||||||
|
<string name="green_color">Зелений</string>
|
||||||
|
<string name="blue_color">Синій</string>
|
||||||
|
|
||||||
<!-- Dark Theme properties -->
|
<!-- Dark Theme properties -->
|
||||||
<string name="dark_theme_off">Вимкнено</string>
|
<string name="dark_theme_off">Вимкнено</string>
|
||||||
<string name="dark_theme_on">Ввімкнено</string>
|
<string name="dark_theme_on">Ввімкнено</string>
|
||||||
<string name="dark_theme_follow_system">Тема системи</string>
|
<string name="dark_theme_follow_system">Тема системи</string>
|
||||||
|
|
||||||
|
<!-- Font Style properties -->
|
||||||
|
<string name="font_style_italic">Похилий</string>
|
||||||
|
<string name="font_style_normal">Нормальний</string>
|
||||||
|
|
||||||
<!-- Themes -->
|
<!-- Themes -->
|
||||||
<string name="dynamic_theme">Хаос</string>
|
<string name="dynamic_theme">Хаос</string>
|
||||||
<string name="blue_theme">Нептун</string>
|
<string name="blue_theme">Нептун</string>
|
||||||
|
|
@ -147,4 +178,15 @@
|
||||||
<string name="file_size">Розмір файлу</string>
|
<string name="file_size">Розмір файлу</string>
|
||||||
<string name="unknown">Невідомо</string>
|
<string name="unknown">Невідомо</string>
|
||||||
|
|
||||||
|
<!-- Reader -->
|
||||||
|
<string name="loading">Завантаження…</string>
|
||||||
|
<string name="happy_reading">Приємного читання!</string>
|
||||||
|
<string name="thanks_for_reading">
|
||||||
|
Дякуємо за те, що прочитали \"%1$s\" з нами.
|
||||||
|
Ми сподіваємось, що вам сподобалася ця книга!
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<!-- Font families -->
|
||||||
|
<string name="default_font">За замовчуванням</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -52,6 +52,12 @@
|
||||||
<string name="error_no_description">
|
<string name="error_no_description">
|
||||||
No description.
|
No description.
|
||||||
</string>
|
</string>
|
||||||
|
<string name="error_file_not_found">
|
||||||
|
File was not found. Please check whether this book\'s file still exist.
|
||||||
|
</string>
|
||||||
|
<string name="error_something_went_wrong">
|
||||||
|
Something went wrong. Please check whether this book\'s file is not corrupted.
|
||||||
|
</string>
|
||||||
|
|
||||||
<!-- Is Empty messages -->
|
<!-- Is Empty messages -->
|
||||||
<string name="browse_empty">
|
<string name="browse_empty">
|
||||||
|
|
@ -65,6 +71,7 @@
|
||||||
<string name="selected_items_count_query">%1$s selected</string>
|
<string name="selected_items_count_query">%1$s selected</string>
|
||||||
<string name="search_query">Search %1$s…</string>
|
<string name="search_query">Search %1$s…</string>
|
||||||
<string name="you_read_query">You read %1$s of this book.</string>
|
<string name="you_read_query">You read %1$s of this book.</string>
|
||||||
|
<string name="read_query">%1$s read</string>
|
||||||
|
|
||||||
<!-- Action Texts -->
|
<!-- Action Texts -->
|
||||||
<string name="cancel">Cancel</string>
|
<string name="cancel">Cancel</string>
|
||||||
|
|
@ -78,6 +85,9 @@
|
||||||
<string name="start">Start</string>
|
<string name="start">Start</string>
|
||||||
<string name="continue_read">Continue</string>
|
<string name="continue_read">Continue</string>
|
||||||
<string name="never">Never</string>
|
<string name="never">Never</string>
|
||||||
|
<string name="go_back">Go back</string>
|
||||||
|
<string name="move_to_read">Move this book to \"Already Read\"</string>
|
||||||
|
<string name="back_to_library">Back to Library</string>
|
||||||
|
|
||||||
<!-- Items -->
|
<!-- Items -->
|
||||||
<string name="files">files</string>
|
<string name="files">files</string>
|
||||||
|
|
@ -97,23 +107,44 @@
|
||||||
<string name="already_read_tab">Already Read</string>
|
<string name="already_read_tab">Already Read</string>
|
||||||
<string name="planning_tab">Planning</string>
|
<string name="planning_tab">Planning</string>
|
||||||
<string name="dropped_tab">Dropped</string>
|
<string name="dropped_tab">Dropped</string>
|
||||||
|
<string name="general_tab">General</string>
|
||||||
|
<string name="color_tab">Colors</string>
|
||||||
|
|
||||||
<!-- Settings categories -->
|
<!-- Settings categories -->
|
||||||
<string name="general_settings">General</string>
|
<string name="general_settings">General</string>
|
||||||
<string name="general_settings_desc">App language</string>
|
<string name="general_settings_desc">App language</string>
|
||||||
<string name="appearance_settings">Appearance</string>
|
<string name="appearance_settings">Appearance</string>
|
||||||
<string name="appearance_settings_desc">App theme, dark theme</string>
|
<string name="appearance_settings_desc">App theme, dark theme</string>
|
||||||
|
<string name="font_settings">Font</string>
|
||||||
|
<string name="text_settings">Text</string>
|
||||||
|
|
||||||
<!-- Settings options -->
|
<!-- Settings options -->
|
||||||
<string name="language_option">App language</string>
|
<string name="language_option">App language</string>
|
||||||
<string name="dark_theme_option">Dark Theme</string>
|
<string name="dark_theme_option">Dark theme</string>
|
||||||
<string name="app_theme_option">App theme</string>
|
<string name="app_theme_option">App theme</string>
|
||||||
|
<string name="font_family_option">Font family</string>
|
||||||
|
<string name="font_style_option">Font style</string>
|
||||||
|
<string name="font_size_option">Font size</string>
|
||||||
|
<string name="line_height_option">Line height</string>
|
||||||
|
<string name="paragraph_height_option">Paragraph height</string>
|
||||||
|
<string name="paragraph_indentation_option">Paragraph indentation</string>
|
||||||
|
<string name="background_color_option">Background color</string>
|
||||||
|
<string name="font_color_option">Font color</string>
|
||||||
|
|
||||||
|
<!-- Colors -->
|
||||||
|
<string name="red_color">Red</string>
|
||||||
|
<string name="green_color">Green</string>
|
||||||
|
<string name="blue_color">Blue</string>
|
||||||
|
|
||||||
<!-- Dark Theme properties -->
|
<!-- Dark Theme properties -->
|
||||||
<string name="dark_theme_off">Disabled</string>
|
<string name="dark_theme_off">Disabled</string>
|
||||||
<string name="dark_theme_on">Enabled</string>
|
<string name="dark_theme_on">Enabled</string>
|
||||||
<string name="dark_theme_follow_system">Follow system</string>
|
<string name="dark_theme_follow_system">Follow system</string>
|
||||||
|
|
||||||
|
<!-- Font Style properties -->
|
||||||
|
<string name="font_style_italic">Italic</string>
|
||||||
|
<string name="font_style_normal">Normal</string>
|
||||||
|
|
||||||
<!-- Themes -->
|
<!-- Themes -->
|
||||||
<string name="dynamic_theme">Chaos</string>
|
<string name="dynamic_theme">Chaos</string>
|
||||||
<string name="blue_theme">Neptune</string>
|
<string name="blue_theme">Neptune</string>
|
||||||
|
|
@ -148,4 +179,15 @@
|
||||||
<string name="file_size">File size</string>
|
<string name="file_size">File size</string>
|
||||||
<string name="unknown">Unknown</string>
|
<string name="unknown">Unknown</string>
|
||||||
|
|
||||||
|
<!-- Reader -->
|
||||||
|
<string name="loading">Loading…</string>
|
||||||
|
<string name="happy_reading">Happy reading!</string>
|
||||||
|
<string name="thanks_for_reading">
|
||||||
|
Thank you for reading \"%1$s\" with us.
|
||||||
|
We hope you enjoyed this book!
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<!-- Font families -->
|
||||||
|
<string name="default_font">Default</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Start.Splash" parent="Theme.SplashScreen">
|
<style name="Theme.Start.Splash" parent="Theme.SplashScreen">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue