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 {
|
||||
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
|
||||
|
||||
// Enables resource shrinking, which is performed by the
|
||||
// Android Gradle plugin.
|
||||
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(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
|
||||
|
||||
// release {
|
||||
// isMinifyEnabled = false
|
||||
// proguardFiles(
|
||||
// getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
// "proguard-rules.pro"
|
||||
// )
|
||||
// signingConfig = signingConfigs.getByName("debug")
|
||||
// }
|
||||
}
|
||||
compileOptions {
|
||||
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.Screen
|
||||
import com.acclorite.books_history.presentation.components.BottomNavigationBar
|
||||
import com.acclorite.books_history.presentation.main.MainEvent
|
||||
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.presentation.screens.book_info.BookInfoScreen
|
||||
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.reader.ReaderScreen
|
||||
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.general.GeneralSettings
|
||||
|
|
@ -93,6 +95,10 @@ class MainActivity : ComponentActivity() {
|
|||
)
|
||||
}
|
||||
|
||||
composable(screen = Screen.HISTORY) {
|
||||
HistoryScreen(navigator = this@NavigationHost)
|
||||
}
|
||||
|
||||
composable(screen = Screen.BROWSE) {
|
||||
BrowseScreen(navigator = this@NavigationHost)
|
||||
}
|
||||
|
|
@ -106,11 +112,20 @@ class MainActivity : ComponentActivity() {
|
|||
enterAnim = Transitions.SlidingTransitionIn,
|
||||
exitAnim = Transitions.SlidingTransitionOut
|
||||
) {
|
||||
|
||||
BookInfoScreen(
|
||||
navigator = this@NavigationHost
|
||||
)
|
||||
}
|
||||
composable(
|
||||
screen = Screen.READER,
|
||||
enterAnim = Transitions.SlidingTransitionIn,
|
||||
exitAnim = Transitions.SlidingTransitionOut
|
||||
) {
|
||||
ReaderScreen(
|
||||
navigator = this@NavigationHost,
|
||||
mainViewModel = mainViewModel
|
||||
)
|
||||
}
|
||||
|
||||
// Settings
|
||||
composable(
|
||||
|
|
@ -119,7 +134,6 @@ class MainActivity : ComponentActivity() {
|
|||
exitAnim = Transitions.SlidingTransitionOut
|
||||
) {
|
||||
SettingsScreen(
|
||||
viewModel = mainViewModel,
|
||||
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.ui.text.font.FontFamily
|
||||
import com.acclorite.books_history.util.UIText
|
||||
|
||||
@Immutable
|
||||
data class FontWithName(
|
||||
val fontName: String,
|
||||
val id: String,
|
||||
val fontName: UIText,
|
||||
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 arguments = mutableStateListOf<Argument>()
|
||||
|
||||
private fun putArgument(argument: Argument) {
|
||||
fun putArgument(argument: Argument) {
|
||||
var found = false
|
||||
|
||||
for ((index, arg) in arguments.withIndex()) {
|
||||
|
|
@ -115,6 +115,19 @@ class Navigator @AssistedInject constructor(
|
|||
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) {
|
||||
if (canGoBack()) {
|
||||
savedStateHandle[CURRENT_SCREEN] = backStack.value.last()
|
||||
|
|
@ -126,10 +139,6 @@ class Navigator @AssistedInject constructor(
|
|||
return backStack.value.isNotEmpty()
|
||||
}
|
||||
|
||||
fun clearBackStack() {
|
||||
backStack.value.clear()
|
||||
}
|
||||
|
||||
fun getCurrentScreen(): StateFlow<Screen> {
|
||||
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.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -60,6 +61,7 @@ fun CustomDialogWithContent(
|
|||
)
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding()
|
||||
.statusBarsPadding()
|
||||
.clip(MaterialTheme.shapes.extraLarge)
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ fun CustomDialogWithLazyColumn(
|
|||
)
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding()
|
||||
.statusBarsPadding()
|
||||
.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 {
|
||||
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 androidx.activity.ComponentActivity
|
||||
|
|
@ -109,7 +109,7 @@ class MainViewModel @Inject constructor(
|
|||
|
||||
/* -- Font Family -------------------------------------------------- */
|
||||
private var _fontFamily: String =
|
||||
stateHandle[Constants.FONT] ?: Constants.FONTS[0].fontName
|
||||
stateHandle[Constants.FONT] ?: Constants.FONTS[0].id
|
||||
set(value) {
|
||||
field = value
|
||||
stateHandle[Constants.FONT] = value
|
||||
|
|
@ -204,6 +204,63 @@ class MainViewModel @Inject constructor(
|
|||
_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
|
||||
.execute(DataStoreConstants.BACKGROUND_COLOR, _backgroundColor)
|
||||
.first {
|
||||
setDatastore.execute(DataStoreConstants.BACKGROUND_COLOR, it)
|
||||
_backgroundColor = it
|
||||
|
||||
onEvent(MainEvent.OnChangeBackgroundColor(it))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -283,9 +338,7 @@ class MainViewModel @Inject constructor(
|
|||
getDatastore
|
||||
.execute(DataStoreConstants.FONT_COLOR, _fontColor)
|
||||
.first {
|
||||
setDatastore.execute(DataStoreConstants.FONT_COLOR, it)
|
||||
_fontColor = it
|
||||
|
||||
onEvent(MainEvent.OnChangeFontColor(it))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -295,10 +348,7 @@ class MainViewModel @Inject constructor(
|
|||
getDatastore
|
||||
.execute(DataStoreConstants.FONT, _fontFamily)
|
||||
.first {
|
||||
setDatastore.execute(DataStoreConstants.FONT, it)
|
||||
_fontFamily = Constants.FONTS.find { font -> font.fontName == it }?.fontName
|
||||
?: Constants.FONTS[0].fontName
|
||||
|
||||
onEvent(MainEvent.OnChangeFontFamily(it))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -308,9 +358,7 @@ class MainViewModel @Inject constructor(
|
|||
getDatastore
|
||||
.execute(DataStoreConstants.IS_ITALIC, _isItalic)
|
||||
.first {
|
||||
setDatastore.execute(DataStoreConstants.IS_ITALIC, it)
|
||||
_isItalic = it
|
||||
|
||||
onEvent(MainEvent.OnChangeFontStyle(it))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -320,9 +368,7 @@ class MainViewModel @Inject constructor(
|
|||
getDatastore
|
||||
.execute(DataStoreConstants.FONT_SIZE, _fontSize)
|
||||
.first {
|
||||
setDatastore.execute(DataStoreConstants.FONT_SIZE, it)
|
||||
_fontSize = it
|
||||
|
||||
onEvent(MainEvent.OnChangeFontSize(it))
|
||||
it > 0
|
||||
}
|
||||
}
|
||||
|
|
@ -332,9 +378,7 @@ class MainViewModel @Inject constructor(
|
|||
getDatastore
|
||||
.execute(DataStoreConstants.LINE_HEIGHT, _lineHeight)
|
||||
.first {
|
||||
setDatastore.execute(DataStoreConstants.LINE_HEIGHT, it)
|
||||
_lineHeight = it
|
||||
|
||||
onEvent(MainEvent.OnChangeLineHeight(it))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -344,9 +388,7 @@ class MainViewModel @Inject constructor(
|
|||
getDatastore
|
||||
.execute(DataStoreConstants.PARAGRAPH_HEIGHT, _paragraphHeight)
|
||||
.first {
|
||||
setDatastore.execute(DataStoreConstants.PARAGRAPH_HEIGHT, it)
|
||||
_paragraphHeight = it
|
||||
|
||||
onEvent(MainEvent.OnChangeParagraphHeight(it))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -356,9 +398,7 @@ class MainViewModel @Inject constructor(
|
|||
getDatastore
|
||||
.execute(DataStoreConstants.PARAGRAPH_INDENTATION, _paragraphIndentation)
|
||||
.first {
|
||||
setDatastore.execute(DataStoreConstants.PARAGRAPH_INDENTATION, it)
|
||||
_paragraphIndentation = it
|
||||
|
||||
onEvent(MainEvent.OnChangeParagraphIndentation(it))
|
||||
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.WindowInsets
|
||||
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.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||
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.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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.acclorite.books_history.R
|
||||
|
|
@ -95,7 +99,9 @@ fun BookInfoScreen(
|
|||
|
||||
val state by viewModel.state.collectAsState()
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
val scrollState = rememberScrollState()
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
val firstVisibleItemIndex by remember { derivedStateOf { listState.firstVisibleItemIndex } }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
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 = {
|
||||
Box {
|
||||
DefaultTransition(
|
||||
|
|
@ -206,34 +222,44 @@ fun BookInfoScreen(
|
|||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
if (state.book.coverImage != null) {
|
||||
BookInfoBackground(image = state.book.coverImage!!.asImageBitmap())
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding())
|
||||
) {
|
||||
Column(
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState)
|
||||
.padding(horizontal = 24.dp)
|
||||
.fillMaxSize(),
|
||||
state = listState
|
||||
) {
|
||||
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()) {
|
||||
Spacer(modifier = Modifier.height(paddingValues.calculateTopPadding() + 12.dp))
|
||||
// Info
|
||||
BookInfoInfoSection(viewModel = viewModel, book = state.book)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
// Statistic
|
||||
BookInfoStatisticSection(book = state.book)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
// Description
|
||||
BookInfoDescriptionSection(book = state.book)
|
||||
}
|
||||
}
|
||||
|
||||
if (state.book.file != null) {
|
||||
FloatingActionButton(
|
||||
|
|
@ -260,7 +286,7 @@ fun BookInfoScreen(
|
|||
Modifier.size(24.dp)
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = scrollState.value < 5
|
||||
visible = !listState.canScrollBackward
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
|
|
|
|||
|
|
@ -1,45 +1,41 @@
|
|||
package com.acclorite.books_history.presentation.screens.book_info.components
|
||||
|
||||
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.height
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun BookInfoBackground(image: ImageBitmap) {
|
||||
fun BookInfoBackground(height: Dp, image: ImageBitmap) {
|
||||
val background = MaterialTheme.colorScheme.background
|
||||
|
||||
Image(
|
||||
bitmap = image,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(0.5f)
|
||||
.blur(1.5.dp),
|
||||
alpha = 0.3f,
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(0.51f)
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
listOf(
|
||||
Color.Transparent,
|
||||
MaterialTheme.colorScheme.surface
|
||||
),
|
||||
startY = 0f,
|
||||
endY = 750f
|
||||
)
|
||||
.height(height)
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawRect(
|
||||
brush = Brush.verticalGradient(
|
||||
0f to Color.Transparent,
|
||||
0.8f to background
|
||||
)
|
||||
)
|
||||
}
|
||||
.blur(2.dp),
|
||||
alpha = 0.3f,
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import androidx.activity.result.PickVisualMediaRequest
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -61,9 +63,11 @@ fun BookInfoChangeCoverBottomSheet(
|
|||
)
|
||||
|
||||
ModalBottomSheet(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onDismissRequest = {
|
||||
viewModel.onEvent(BookInfoEvent.OnShowHideChangeCoverBottomSheet)
|
||||
},
|
||||
sheetState = rememberModalBottomSheetState(true),
|
||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||
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.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -21,7 +22,8 @@ fun BookInfoDescriptionSection(book: Book) {
|
|||
?: stringResource(id = R.string.error_no_description),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
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 androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
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.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -31,7 +33,7 @@ fun BookInfoDetailsBottomSheet(
|
|||
val state by viewModel.state.collectAsState()
|
||||
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 sizeBytes = state.book.file?.length() ?: 0
|
||||
|
|
@ -46,9 +48,11 @@ fun BookInfoDetailsBottomSheet(
|
|||
|
||||
|
||||
ModalBottomSheet(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onDismissRequest = {
|
||||
viewModel.onEvent(BookInfoEvent.OnShowHideDetailsBottomSheet)
|
||||
},
|
||||
sheetState = rememberModalBottomSheetState(true),
|
||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
|
|
@ -54,6 +55,7 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
|
|||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -14,11 +16,16 @@ 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.Book
|
||||
import com.acclorite.books_history.util.removeDigits
|
||||
import com.acclorite.books_history.util.removeTrailingZero
|
||||
import com.acclorite.books_history.presentation.data.removeDigits
|
||||
import com.acclorite.books_history.presentation.data.removeTrailingZero
|
||||
|
||||
@Composable
|
||||
fun BookInfoStatisticSection(book: Book) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "${
|
||||
(book.progress * 100)
|
||||
|
|
@ -59,3 +66,4 @@ fun BookInfoStatisticSection(book: Book) {
|
|||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
|||
import com.google.accompanist.permissions.PermissionState
|
||||
import java.io.File
|
||||
|
||||
sealed class BrowseEvent() {
|
||||
sealed class BrowseEvent {
|
||||
data class OnStoragePermissionRequest(val activity: ComponentActivity) : BrowseEvent()
|
||||
data class OnLegacyStoragePermissionRequest(val permissionState: PermissionState) :
|
||||
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))
|
||||
}
|
||||
},
|
||||
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.unit.dp
|
||||
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.util.removeDigits
|
||||
import com.acclorite.books_history.util.removeTrailingZero
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -26,4 +26,5 @@ sealed class LibraryEvent {
|
|||
data class OnMoveBooks(val pagerState: PagerState) : LibraryEvent()
|
||||
data object OnShowHideDeleteDialog : LibraryEvent()
|
||||
data object OnDeleteBooks : LibraryEvent()
|
||||
data class OnUpdateBook(val book: Book) : LibraryEvent()
|
||||
}
|
||||
|
|
@ -257,6 +257,21 @@ class LibraryViewModel @Inject constructor(
|
|||
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
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
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.presentation.Navigator
|
||||
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.ui.elevation
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
viewModel: MainViewModel,
|
||||
navigator: Navigator
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
|
|
@ -74,12 +70,12 @@ fun SettingsScreen(
|
|||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding())
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
item {
|
||||
SettingsCategoryItem(
|
||||
icon = Icons.Default.DisplaySettings,
|
||||
text = stringResource(id = R.string.general_settings),
|
||||
|
|
@ -87,6 +83,9 @@ fun SettingsScreen(
|
|||
) {
|
||||
navigator.navigate(Screen.GENERAL_SETTINGS)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
SettingsCategoryItem(
|
||||
icon = Icons.Default.Palette,
|
||||
text = stringResource(id = R.string.appearance_settings),
|
||||
|
|
@ -95,5 +94,8 @@ fun SettingsScreen(
|
|||
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
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
|
|
@ -20,39 +18,30 @@ 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.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
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.main.MainViewModel
|
||||
import com.acclorite.books_history.presentation.screens.settings.components.SettingsOptionItem
|
||||
import com.acclorite.books_history.presentation.screens.settings.nested.appearance.components.AppearanceSettingsDarkThemeDialog
|
||||
import com.acclorite.books_history.presentation.data.MainEvent
|
||||
import com.acclorite.books_history.presentation.data.MainViewModel
|
||||
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.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.elevation
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AppearanceSettings(
|
||||
viewModel: AppearanceSettingsViewModel = hiltViewModel(),
|
||||
mainViewModel: MainViewModel,
|
||||
navigator: Navigator
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
val darkTheme = mainViewModel.darkTheme.collectAsState().value!!
|
||||
|
||||
if (state.showDarkThemeDialog) {
|
||||
AppearanceSettingsDarkThemeDialog(viewModel = viewModel, mainViewModel = mainViewModel)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -87,23 +76,38 @@ fun AppearanceSettings(
|
|||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding())
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
SettingsOptionItem(
|
||||
item {
|
||||
ChipsWithTitle(
|
||||
title = stringResource(id = R.string.dark_theme_option),
|
||||
description = when (darkTheme) {
|
||||
chips = DarkTheme.entries.map {
|
||||
ChipItem(
|
||||
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
|
||||
)
|
||||
},
|
||||
) {
|
||||
viewModel.onEvent(AppearanceSettingsEvent.OnShowHideDarkThemeDialog)
|
||||
mainViewModel.onEvent(
|
||||
MainEvent.OnChangeDarkTheme(
|
||||
it.id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
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 androidx.compose.ui.unit.sp
|
||||
import com.acclorite.books_history.R
|
||||
import com.acclorite.books_history.presentation.main.MainEvent
|
||||
import com.acclorite.books_history.presentation.main.MainViewModel
|
||||
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.ui.Theme
|
||||
import com.acclorite.books_history.ui.isDark
|
||||
import com.acclorite.books_history.util.Constants
|
||||
|
|
@ -38,21 +35,18 @@ fun AppearanceSettingsThemeSwitcher(
|
|||
Modifier
|
||||
.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))
|
||||
CategoryTitle(
|
||||
title = stringResource(id = R.string.app_theme_option)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
LazyRow(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
itemsIndexed(themes) { index, themeEntry ->
|
||||
if (index == 0)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Spacer(modifier = Modifier.width(18.dp))
|
||||
|
||||
AppearanceSettingsThemeSwitcherItem(
|
||||
theme = themeEntry,
|
||||
|
|
@ -65,7 +59,7 @@ fun AppearanceSettingsThemeSwitcher(
|
|||
if (index != Theme.entries.lastIndex) {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
} 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))
|
||||
Text(
|
||||
text = theme.second.asString(),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
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
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
|
|
@ -20,38 +19,31 @@ 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.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
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.main.MainViewModel
|
||||
import com.acclorite.books_history.presentation.screens.settings.components.SettingsOptionItem
|
||||
import com.acclorite.books_history.presentation.screens.settings.nested.general.components.GeneralSettingsLanguageDialog
|
||||
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.presentation.data.MainEvent
|
||||
import com.acclorite.books_history.presentation.data.MainViewModel
|
||||
import com.acclorite.books_history.presentation.screens.settings.components.ChipsWithTitle
|
||||
import com.acclorite.books_history.ui.elevation
|
||||
import com.acclorite.books_history.util.Constants
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun GeneralSettings(
|
||||
viewModel: GeneralSettingsViewModel = hiltViewModel(),
|
||||
mainViewModel: MainViewModel,
|
||||
navigator: Navigator
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
val state by viewModel.state.collectAsState()
|
||||
val context = LocalContext.current as ComponentActivity
|
||||
|
||||
val language = mainViewModel.language.collectAsState().value!!
|
||||
|
||||
if (state.showLanguageDialog) {
|
||||
GeneralSettingsLanguageDialog(viewModel = viewModel, mainViewModel = mainViewModel)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -86,18 +78,30 @@ fun GeneralSettings(
|
|||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding())
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
SettingsOptionItem(
|
||||
item {
|
||||
ChipsWithTitle(
|
||||
title = stringResource(id = R.string.language_option),
|
||||
description = Constants.LANGUAGES.find { it.first == language }?.second
|
||||
?: return@Column
|
||||
chips = Constants.LANGUAGES.map {
|
||||
ChipItem(
|
||||
it.first,
|
||||
it.second,
|
||||
MaterialTheme.typography.labelLarge,
|
||||
it.first == language
|
||||
)
|
||||
}
|
||||
) {
|
||||
viewModel.onEvent(GeneralSettingsEvent.OnShowHideLanguageDialog)
|
||||
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.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).isAppearanceLightNavigationBars = !isDark
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ val Typography = Typography(
|
|||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp
|
||||
lineHeight = 20.sp
|
||||
),
|
||||
titleSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
|
|
|
|||
|
|
@ -49,7 +49,13 @@ object Constants {
|
|||
// Fonts for Reader
|
||||
val FONTS = listOf(
|
||||
FontWithName(
|
||||
"Raleway",
|
||||
"default",
|
||||
UIText.StringResource(R.string.default_font),
|
||||
FontFamily.Default
|
||||
),
|
||||
FontWithName(
|
||||
"raleway",
|
||||
UIText.StringValue("Raleway"),
|
||||
FontFamily(
|
||||
Font(R.font.raleway_regular),
|
||||
Font(R.font.raleway_regular_italic, style = FontStyle.Italic),
|
||||
|
|
@ -60,7 +66,8 @@ object Constants {
|
|||
)
|
||||
),
|
||||
FontWithName(
|
||||
"Open Sans",
|
||||
"open_sans",
|
||||
UIText.StringValue("Open Sans"),
|
||||
FontFamily(
|
||||
Font(R.font.opensans_regular),
|
||||
Font(R.font.opensans_regular_italic, style = FontStyle.Italic),
|
||||
|
|
@ -71,7 +78,8 @@ object Constants {
|
|||
)
|
||||
),
|
||||
FontWithName(
|
||||
"Mulish",
|
||||
"mulish",
|
||||
UIText.StringValue("Mulish"),
|
||||
FontFamily(
|
||||
Font(R.font.mulish_regular),
|
||||
Font(R.font.mulish_regular_italic, style = FontStyle.Italic),
|
||||
|
|
@ -82,7 +90,8 @@ object Constants {
|
|||
)
|
||||
),
|
||||
FontWithName(
|
||||
"Arimo",
|
||||
"arimo",
|
||||
UIText.StringValue("Arimo"),
|
||||
FontFamily(
|
||||
Font(R.font.arimo_regular),
|
||||
Font(R.font.arimo_regular_italic, style = FontStyle.Italic),
|
||||
|
|
@ -93,7 +102,8 @@ object Constants {
|
|||
)
|
||||
),
|
||||
FontWithName(
|
||||
"Garamond",
|
||||
"garamond",
|
||||
UIText.StringValue("Garamond"),
|
||||
FontFamily(
|
||||
Font(R.font.garamond_regular),
|
||||
Font(R.font.garamond_regular_italic, style = FontStyle.Italic),
|
||||
|
|
@ -104,7 +114,8 @@ object Constants {
|
|||
)
|
||||
),
|
||||
FontWithName(
|
||||
"Roboto Serif",
|
||||
"roboto_serif",
|
||||
UIText.StringValue("Roboto Serif"),
|
||||
FontFamily(
|
||||
Font(R.font.robotoserif_regular),
|
||||
Font(R.font.robotoserif_regular_italic, style = FontStyle.Italic),
|
||||
|
|
@ -115,7 +126,8 @@ object Constants {
|
|||
)
|
||||
),
|
||||
FontWithName(
|
||||
"Noto Serif",
|
||||
"noto_serif",
|
||||
UIText.StringValue("Noto Serif"),
|
||||
FontFamily(
|
||||
Font(R.font.notoserif_regular),
|
||||
Font(R.font.notoserif_regular_italic, style = FontStyle.Italic),
|
||||
|
|
@ -126,7 +138,8 @@ object Constants {
|
|||
)
|
||||
),
|
||||
FontWithName(
|
||||
"Noto Sans",
|
||||
"noto_sans",
|
||||
UIText.StringValue("Noto Sans"),
|
||||
FontFamily(
|
||||
Font(R.font.notosans_regular),
|
||||
Font(R.font.notosans_regular_italic, style = FontStyle.Italic),
|
||||
|
|
@ -137,7 +150,8 @@ object Constants {
|
|||
)
|
||||
),
|
||||
FontWithName(
|
||||
"Roboto",
|
||||
"roboto",
|
||||
UIText.StringValue("Roboto"),
|
||||
FontFamily(
|
||||
Font(R.font.roboto_regular),
|
||||
Font(R.font.roboto_regular_italic, style = FontStyle.Italic),
|
||||
|
|
@ -148,7 +162,8 @@ object Constants {
|
|||
)
|
||||
),
|
||||
FontWithName(
|
||||
"Jost",
|
||||
"jost",
|
||||
UIText.StringValue("Jost"),
|
||||
FontFamily(
|
||||
Font(R.font.jost_regular),
|
||||
Font(R.font.jost_regular_italic, style = FontStyle.Italic),
|
||||
|
|
|
|||
|
|
@ -51,6 +51,12 @@
|
|||
<string name="error_no_description">
|
||||
Немає опису.
|
||||
</string>
|
||||
<string name="error_file_not_found">
|
||||
Файл не знайдено. Будь ласка, перевірте чи файл цієї книги все ще існує.
|
||||
</string>
|
||||
<string name="error_something_went_wrong">
|
||||
Щось пішло не так. Будь ласка, перевірте чи файл цієї книги не пошкоджений.
|
||||
</string>
|
||||
|
||||
<!-- Is Empty messages -->
|
||||
<string name="browse_empty">
|
||||
|
|
@ -64,6 +70,7 @@
|
|||
<string name="selected_items_count_query">%1$s обрано</string>
|
||||
<string name="search_query">Знайти %1$s…</string>
|
||||
<string name="you_read_query">Ви прочитали %1$s цієї книги.</string>
|
||||
<string name="read_query">%1$s прочитано</string>
|
||||
|
||||
<!-- Action Texts -->
|
||||
<string name="cancel">Відмінити</string>
|
||||
|
|
@ -77,6 +84,9 @@
|
|||
<string name="start">Почати</string>
|
||||
<string name="continue_read">Продовжити</string>
|
||||
<string name="never">Ніколи</string>
|
||||
<string name="go_back">Назад</string>
|
||||
<string name="move_to_read">Перемістити цю книгу в \"Прочитано\"</string>
|
||||
<string name="back_to_library">Назад в Бібліотеку</string>
|
||||
|
||||
<!-- Items -->
|
||||
<string name="files">файли</string>
|
||||
|
|
@ -96,23 +106,44 @@
|
|||
<string name="already_read_tab">Прочитано</string>
|
||||
<string name="planning_tab">Планую</string>
|
||||
<string name="dropped_tab">Кинуто</string>
|
||||
<string name="general_tab">Головні</string>
|
||||
<string name="color_tab">Кольори</string>
|
||||
|
||||
<!-- Settings categories -->
|
||||
<string name="general_settings">Головні</string>
|
||||
<string name="general_settings_desc">Мова застосунку</string>
|
||||
<string name="appearance_settings">Зовнішній вигляд</string>
|
||||
<string name="appearance_settings_desc">Тема застосунку, темна тема</string>
|
||||
<string name="font_settings">Шрифт</string>
|
||||
<string name="text_settings">Текст</string>
|
||||
|
||||
<!-- Settings options -->
|
||||
<string name="language_option">Мова застосунку</string>
|
||||
<string name="dark_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 -->
|
||||
<string name="dark_theme_off">Вимкнено</string>
|
||||
<string name="dark_theme_on">Ввімкнено</string>
|
||||
<string name="dark_theme_follow_system">Тема системи</string>
|
||||
|
||||
<!-- Font Style properties -->
|
||||
<string name="font_style_italic">Похилий</string>
|
||||
<string name="font_style_normal">Нормальний</string>
|
||||
|
||||
<!-- Themes -->
|
||||
<string name="dynamic_theme">Хаос</string>
|
||||
<string name="blue_theme">Нептун</string>
|
||||
|
|
@ -147,4 +178,15 @@
|
|||
<string name="file_size">Розмір файлу</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>
|
||||
|
|
@ -52,6 +52,12 @@
|
|||
<string name="error_no_description">
|
||||
No description.
|
||||
</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 -->
|
||||
<string name="browse_empty">
|
||||
|
|
@ -65,6 +71,7 @@
|
|||
<string name="selected_items_count_query">%1$s selected</string>
|
||||
<string name="search_query">Search %1$s…</string>
|
||||
<string name="you_read_query">You read %1$s of this book.</string>
|
||||
<string name="read_query">%1$s read</string>
|
||||
|
||||
<!-- Action Texts -->
|
||||
<string name="cancel">Cancel</string>
|
||||
|
|
@ -78,6 +85,9 @@
|
|||
<string name="start">Start</string>
|
||||
<string name="continue_read">Continue</string>
|
||||
<string name="never">Never</string>
|
||||
<string name="go_back">Go back</string>
|
||||
<string name="move_to_read">Move this book to \"Already Read\"</string>
|
||||
<string name="back_to_library">Back to Library</string>
|
||||
|
||||
<!-- Items -->
|
||||
<string name="files">files</string>
|
||||
|
|
@ -97,23 +107,44 @@
|
|||
<string name="already_read_tab">Already Read</string>
|
||||
<string name="planning_tab">Planning</string>
|
||||
<string name="dropped_tab">Dropped</string>
|
||||
<string name="general_tab">General</string>
|
||||
<string name="color_tab">Colors</string>
|
||||
|
||||
<!-- Settings categories -->
|
||||
<string name="general_settings">General</string>
|
||||
<string name="general_settings_desc">App language</string>
|
||||
<string name="appearance_settings">Appearance</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 -->
|
||||
<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="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 -->
|
||||
<string name="dark_theme_off">Disabled</string>
|
||||
<string name="dark_theme_on">Enabled</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 -->
|
||||
<string name="dynamic_theme">Chaos</string>
|
||||
<string name="blue_theme">Neptune</string>
|
||||
|
|
@ -148,4 +179,15 @@
|
|||
<string name="file_size">File size</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>
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Start.Splash" parent="Theme.SplashScreen">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue