From 0b3a31a18380d44e2b5ec94fff3c6f59bf096c2d Mon Sep 17 00:00:00 2001 From: acclorite Date: Fri, 1 Mar 2024 02:11:34 +0200 Subject: [PATCH] 0.7.0 - Reader Screen --- app/build.gradle.kts | 19 - .../acclorite/books_history/MainActivity.kt | 22 +- .../books_history/domain/model/ChipItem.kt | 12 + .../domain/model/FontWithName.kt | 4 +- .../books_history/domain/use_case/GetText.kt | 15 + .../books_history/presentation/Navigation.kt | 19 +- .../presentation/components/CategoryTitle.kt | 36 ++ .../components/CustomDialogWithContent.kt | 2 + .../components/CustomDialogWithLazyColumn.kt | 1 + .../data}/ArithmeticOperations.kt | 2 +- .../presentation/data/MainEvent.kt | 19 + .../{main => data}/MainViewModel.kt | 94 +++-- .../presentation/main/MainEvent.kt | 11 - .../screens/book_info/BookInfoScreen.kt | 72 ++-- .../components/BookInfoBackground.kt | 38 +- .../BookInfoChangeCoverBottomSheet.kt | 4 + .../components/BookInfoDescriptionSection.kt | 4 +- .../components/BookInfoDetailsBottomSheet.kt | 6 +- .../components/BookInfoInfoSection.kt | 2 + .../components/BookInfoStatisticSection.kt | 84 +++-- .../screens/browse/data/BrowseEvent.kt | 2 +- .../screens/history/HistoryScreen.kt | 16 + .../screens/history/data/HistoryEvent.kt | 3 + .../screens/history/data/HistoryState.kt | 5 + .../screens/history/data/HistoryViewModel.kt | 36 ++ .../screens/library/LibraryScreen.kt | 7 +- .../library/components/LibraryBookItem.kt | 4 +- .../screens/library/data/LibraryEvent.kt | 1 + .../screens/library/data/LibraryViewModel.kt | 15 + .../screens/reader/ReaderScreen.kt | 324 ++++++++++++++++++ .../reader/components/ReaderBottomBar.kt | 98 ++++++ .../reader/components/ReaderEndItem.kt | 109 ++++++ .../components/ReaderSettingsBottomSheet.kt | 300 ++++++++++++++++ .../ReaderSettingsBottomSheetItem.kt | 50 +++ .../ReaderSettingsBottomSheetTabRow.kt | 73 ++++ .../reader/components/ReaderStartItem.kt | 104 ++++++ .../screens/reader/components/ReaderTopBar.kt | 119 +++++++ .../screens/reader/data/ReaderEvent.kt | 40 +++ .../screens/reader/data/ReaderState.kt | 18 + .../screens/reader/data/ReaderViewModel.kt | 293 ++++++++++++++++ .../screens/settings/SettingsScreen.kt | 40 ++- .../settings/components/CheckboxWIthTitle.kt | 41 +++ .../settings/components/ChipsWithTitle.kt | 77 +++++ .../components/ColorPickerWithTitle.kt | 129 +++++++ .../settings/components/SliderWithTitle.kt | 93 +++++ .../nested/appearance/AppearanceSettings.kt | 58 ++-- .../AppearanceSettingsDarkThemeDialog.kt | 58 ---- .../AppearanceSettingsThemeSwitcher.kt | 24 +- .../AppearanceSettingsThemeSwitcherItem.kt | 4 +- .../data/AppearanceSettingsEvent.kt | 5 - .../data/AppearanceSettingsState.kt | 8 - .../data/AppearanceSettingsViewModel.kt | 31 -- .../nested/general/GeneralSettings.kt | 52 +-- .../GeneralSettingsLanguageDialog.kt | 55 --- .../general/data/GeneralSettingsEvent.kt | 5 - .../general/data/GeneralSettingsState.kt | 8 - .../general/data/GeneralSettingsViewModel.kt | 43 --- .../com/acclorite/books_history/ui/Theme.kt | 5 + .../acclorite/books_history/ui/Typography.kt | 2 +- .../acclorite/books_history/util/Constants.kt | 35 +- app/src/main/res/values-uk/strings.xml | 42 +++ app/src/main/res/values/strings.xml | 44 ++- app/src/main/res/values/themes.xml | 1 + 63 files changed, 2475 insertions(+), 468 deletions(-) create mode 100644 app/src/main/java/com/acclorite/books_history/domain/model/ChipItem.kt create mode 100644 app/src/main/java/com/acclorite/books_history/domain/use_case/GetText.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/components/CategoryTitle.kt rename app/src/main/java/com/acclorite/books_history/{util => presentation/data}/ArithmeticOperations.kt (83%) create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/data/MainEvent.kt rename app/src/main/java/com/acclorite/books_history/presentation/{main => data}/MainViewModel.kt (83%) delete mode 100644 app/src/main/java/com/acclorite/books_history/presentation/main/MainEvent.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/history/HistoryScreen.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/history/data/HistoryEvent.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/history/data/HistoryState.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/history/data/HistoryViewModel.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/reader/ReaderScreen.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderBottomBar.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderEndItem.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderSettingsBottomSheet.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderSettingsBottomSheetItem.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderSettingsBottomSheetTabRow.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderStartItem.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderTopBar.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/reader/data/ReaderEvent.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/reader/data/ReaderState.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/reader/data/ReaderViewModel.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/CheckboxWIthTitle.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/ChipsWithTitle.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/ColorPickerWithTitle.kt create mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/SliderWithTitle.kt delete mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/components/AppearanceSettingsDarkThemeDialog.kt delete mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/data/AppearanceSettingsEvent.kt delete mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/data/AppearanceSettingsState.kt delete mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/data/AppearanceSettingsViewModel.kt delete mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/components/GeneralSettingsLanguageDialog.kt delete mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/data/GeneralSettingsEvent.kt delete mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/data/GeneralSettingsState.kt delete mode 100644 app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/data/GeneralSettingsViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9c123d3d..3d7ddf56 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 diff --git a/app/src/main/java/com/acclorite/books_history/MainActivity.kt b/app/src/main/java/com/acclorite/books_history/MainActivity.kt index ca1cad3b..72e285a7 100644 --- a/app/src/main/java/com/acclorite/books_history/MainActivity.kt +++ b/app/src/main/java/com/acclorite/books_history/MainActivity.kt @@ -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 ) } diff --git a/app/src/main/java/com/acclorite/books_history/domain/model/ChipItem.kt b/app/src/main/java/com/acclorite/books_history/domain/model/ChipItem.kt new file mode 100644 index 00000000..759ecec0 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/domain/model/ChipItem.kt @@ -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 +) diff --git a/app/src/main/java/com/acclorite/books_history/domain/model/FontWithName.kt b/app/src/main/java/com/acclorite/books_history/domain/model/FontWithName.kt index 3c46dae5..a423a6ea 100644 --- a/app/src/main/java/com/acclorite/books_history/domain/model/FontWithName.kt +++ b/app/src/main/java/com/acclorite/books_history/domain/model/FontWithName.kt @@ -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 ) \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/domain/use_case/GetText.kt b/app/src/main/java/com/acclorite/books_history/domain/use_case/GetText.kt new file mode 100644 index 00000000..ef7bdcb1 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/domain/use_case/GetText.kt @@ -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>> { + return repository.getBookTextFromFile(file) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/Navigation.kt b/app/src/main/java/com/acclorite/books_history/presentation/Navigation.kt index 716dd7c2..b8d2581d 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/Navigation.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/Navigation.kt @@ -75,7 +75,7 @@ class Navigator @AssistedInject constructor( private val backStack = savedStateHandle.getStateFlow(BACKSTACK, mutableListOf()) private val arguments = mutableStateListOf() - 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 { return currentScreen } diff --git a/app/src/main/java/com/acclorite/books_history/presentation/components/CategoryTitle.kt b/app/src/main/java/com/acclorite/books_history/presentation/components/CategoryTitle.kt new file mode 100644 index 00000000..7e09177f --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/components/CategoryTitle.kt @@ -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 + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/components/CustomDialogWithContent.kt b/app/src/main/java/com/acclorite/books_history/presentation/components/CustomDialogWithContent.kt index 647de3df..483f573e 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/components/CustomDialogWithContent.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/components/CustomDialogWithContent.kt @@ -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) diff --git a/app/src/main/java/com/acclorite/books_history/presentation/components/CustomDialogWithLazyColumn.kt b/app/src/main/java/com/acclorite/books_history/presentation/components/CustomDialogWithLazyColumn.kt index 1c1ce798..1be5db0b 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/components/CustomDialogWithLazyColumn.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/components/CustomDialogWithLazyColumn.kt @@ -62,6 +62,7 @@ fun CustomDialogWithLazyColumn( ) Column( modifier = modifier + .fillMaxWidth() .navigationBarsPadding() .statusBarsPadding() .clip(MaterialTheme.shapes.extraLarge) diff --git a/app/src/main/java/com/acclorite/books_history/util/ArithmeticOperations.kt b/app/src/main/java/com/acclorite/books_history/presentation/data/ArithmeticOperations.kt similarity index 83% rename from app/src/main/java/com/acclorite/books_history/util/ArithmeticOperations.kt rename to app/src/main/java/com/acclorite/books_history/presentation/data/ArithmeticOperations.kt index 46349645..132bd69f 100644 --- a/app/src/main/java/com/acclorite/books_history/util/ArithmeticOperations.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/data/ArithmeticOperations.kt @@ -1,4 +1,4 @@ -package com.acclorite.books_history.util +package com.acclorite.books_history.presentation.data fun String.removeTrailingZero(): String { if (!this.contains('.')) diff --git a/app/src/main/java/com/acclorite/books_history/presentation/data/MainEvent.kt b/app/src/main/java/com/acclorite/books_history/presentation/data/MainEvent.kt new file mode 100644 index 00000000..b58b5bc2 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/data/MainEvent.kt @@ -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() + +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/main/MainViewModel.kt b/app/src/main/java/com/acclorite/books_history/presentation/data/MainViewModel.kt similarity index 83% rename from app/src/main/java/com/acclorite/books_history/presentation/main/MainViewModel.kt rename to app/src/main/java/com/acclorite/books_history/presentation/data/MainViewModel.kt index 9c3533fe..de2d5e58 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/main/MainViewModel.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/data/MainViewModel.kt @@ -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 } } diff --git a/app/src/main/java/com/acclorite/books_history/presentation/main/MainEvent.kt b/app/src/main/java/com/acclorite/books_history/presentation/main/MainEvent.kt deleted file mode 100644 index f6a9099d..00000000 --- a/app/src/main/java/com/acclorite/books_history/presentation/main/MainEvent.kt +++ /dev/null @@ -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() - -} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/BookInfoScreen.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/BookInfoScreen.kt index 076e7864..c4219230 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/BookInfoScreen.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/BookInfoScreen.kt @@ -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,33 +222,43 @@ 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)) - // Info - BookInfoInfoSection(viewModel = viewModel, book = state.book) + Column(Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.height(paddingValues.calculateTopPadding() + 12.dp)) + // Info + BookInfoInfoSection(viewModel = viewModel, book = state.book) + } + } + } - Spacer(modifier = Modifier.height(24.dp)) - // Statistic - BookInfoStatisticSection(book = state.book) + item { + Spacer(modifier = Modifier.height(24.dp)) + // Statistic + BookInfoStatisticSection(book = state.book) + } - Spacer(modifier = Modifier.height(24.dp)) - // Description - BookInfoDescriptionSection(book = state.book) + item { + Spacer(modifier = Modifier.height(24.dp)) + // Description + BookInfoDescriptionSection(book = state.book) + } } if (state.book.file != null) { @@ -260,7 +286,7 @@ fun BookInfoScreen( Modifier.size(24.dp) ) AnimatedVisibility( - visible = scrollState.value < 5 + visible = !listState.canScrollBackward ) { Text( text = stringResource( diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoBackground.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoBackground.kt index 6840ebd8..0b0367fb 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoBackground.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoBackground.kt @@ -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), + .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 ) - Box( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(0.51f) - .background( - Brush.verticalGradient( - listOf( - Color.Transparent, - MaterialTheme.colorScheme.surface - ), - startY = 0f, - endY = 750f - ) - ) - ) } \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoChangeCoverBottomSheet.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoChangeCoverBottomSheet.kt index a830f5d0..9bd6e2ff 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoChangeCoverBottomSheet.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoChangeCoverBottomSheet.kt @@ -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) ) { diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoDescriptionSection.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoDescriptionSection.kt index 2b5f52c5..d61f2601 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoDescriptionSection.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoDescriptionSection.kt @@ -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) ) } diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoDetailsBottomSheet.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoDetailsBottomSheet.kt index 4daafb56..6595badc 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoDetailsBottomSheet.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoDetailsBottomSheet.kt @@ -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) ) { diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoInfoSection.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoInfoSection.kt index 9934620f..9b3d61b1 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoInfoSection.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoInfoSection.kt @@ -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 diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoStatisticSection.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoStatisticSection.kt index cbe77c4b..44dd912e 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoStatisticSection.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/book_info/components/BookInfoStatisticSection.kt @@ -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,48 +16,54 @@ 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) { - Text( - text = "${ - (book.progress * 100) - .toDouble() - .removeDigits(1) - .removeTrailingZero() - }%", - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge - ) + Column( + Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + ) { + Text( + text = "${ + (book.progress * 100) + .toDouble() + .removeDigits(1) + .removeTrailingZero() + }%", + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge + ) - Spacer(modifier = Modifier.height(6.dp)) + Spacer(modifier = Modifier.height(6.dp)) - LinearProgressIndicator( - book.progress.coerceAtLeast(0.01f), - strokeCap = StrokeCap.Round, - modifier = Modifier - .fillMaxWidth(), - color = MaterialTheme.colorScheme.primary, - trackColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(0.05f) - ) + LinearProgressIndicator( + book.progress.coerceAtLeast(0.01f), + strokeCap = StrokeCap.Round, + modifier = Modifier + .fillMaxWidth(), + color = MaterialTheme.colorScheme.primary, + trackColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(0.05f) + ) - Spacer(modifier = Modifier.height(6.dp)) - Text( - text = stringResource( - id = R.string.you_read_query, - (book.progress * 100) - .toDouble() - .removeDigits(1) - .removeTrailingZero() + "%" - ) + " " + stringResource( - if (book.progress > 0.2f) R.string.read_keep - else R.string.read_more - ), - color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.bodySmall, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = stringResource( + id = R.string.you_read_query, + (book.progress * 100) + .toDouble() + .removeDigits(1) + .removeTrailingZero() + "%" + ) + " " + stringResource( + if (book.progress > 0.2f) R.string.read_keep + else R.string.read_more + ), + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.bodySmall, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/browse/data/BrowseEvent.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/browse/data/BrowseEvent.kt index 492fdfb4..e5939ae7 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/browse/data/BrowseEvent.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/browse/data/BrowseEvent.kt @@ -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() diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/history/HistoryScreen.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/history/HistoryScreen.kt new file mode 100644 index 00000000..aeecb9c8 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/history/HistoryScreen.kt @@ -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() +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/history/data/HistoryEvent.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/history/data/HistoryEvent.kt new file mode 100644 index 00000000..6b699239 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/history/data/HistoryEvent.kt @@ -0,0 +1,3 @@ +package com.acclorite.books_history.presentation.screens.history.data + +sealed class HistoryEvent \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/history/data/HistoryState.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/history/data/HistoryState.kt new file mode 100644 index 00000000..d35f6901 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/history/data/HistoryState.kt @@ -0,0 +1,5 @@ +package com.acclorite.books_history.presentation.screens.history.data + +data class HistoryState( + val foo: Int = 0 +) diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/history/data/HistoryViewModel.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/history/data/HistoryViewModel.kt new file mode 100644 index 00000000..5bce8000 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/history/data/HistoryViewModel.kt @@ -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) { + // + } +} + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/library/LibraryScreen.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/library/LibraryScreen.kt index a0972972..00b0a4b8 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/library/LibraryScreen.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/library/LibraryScreen.kt @@ -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) + ) + } ) } } diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/library/components/LibraryBookItem.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/library/components/LibraryBookItem.kt index 3af2cca4..ef76b3dc 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/library/components/LibraryBookItem.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/library/components/LibraryBookItem.kt @@ -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 diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/library/data/LibraryEvent.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/library/data/LibraryEvent.kt index f3d0aa99..ccea5cd0 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/library/data/LibraryEvent.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/library/data/LibraryEvent.kt @@ -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() } \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/library/data/LibraryViewModel.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/library/data/LibraryViewModel.kt index 8e8baa76..595a543a 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/library/data/LibraryViewModel.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/library/data/LibraryViewModel.kt @@ -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 + ) + } + } } } diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/ReaderScreen.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/ReaderScreen.kt new file mode 100644 index 00000000..82eac8a0 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/ReaderScreen.kt @@ -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( + extrasProducer = { + val book = navigator.retrieveArgument("book") as? Book + if (book == null) { + navigator.navigateBack() + } + + context.defaultViewModelCreationExtras + .withCreationCallback { 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)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderBottomBar.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderBottomBar.kt new file mode 100644 index 00000000..d8b6b422 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderBottomBar.kt @@ -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 + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderEndItem.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderEndItem.kt new file mode 100644 index 00000000..107b4718 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderEndItem.kt @@ -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 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderSettingsBottomSheet.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderSettingsBottomSheet.kt new file mode 100644 index 00000000..bfd4d210 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderSettingsBottomSheet.kt @@ -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 + ) + ) + } + } + } + } +} + + + + + + + + + + + diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderSettingsBottomSheetItem.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderSettingsBottomSheetItem.kt new file mode 100644 index 00000000..b507e8f0 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderSettingsBottomSheetItem.kt @@ -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, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderSettingsBottomSheetTabRow.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderSettingsBottomSheetTabRow.kt new file mode 100644 index 00000000..53529321 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderSettingsBottomSheetTabRow.kt @@ -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 + ) + } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderStartItem.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderStartItem.kt new file mode 100644 index 00000000..732857a0 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderStartItem.kt @@ -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 + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderTopBar.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderTopBar.kt new file mode 100644 index 00000000..ffd53524 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/components/ReaderTopBar.kt @@ -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 + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/data/ReaderEvent.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/data/ReaderEvent.kt new file mode 100644 index 00000000..f858a7c5 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/data/ReaderEvent.kt @@ -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() +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/data/ReaderState.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/data/ReaderState.kt new file mode 100644 index 00000000..5da45419 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/data/ReaderState.kt @@ -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, + + + ) \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/data/ReaderViewModel.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/data/ReaderViewModel.kt new file mode 100644 index 00000000..ed2cfbea --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/reader/data/ReaderViewModel.kt @@ -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 + } +} + + + + + + + + + + + diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/SettingsScreen.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/SettingsScreen.kt index 3825e984..06509c70 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/SettingsScreen.kt @@ -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,26 +70,32 @@ fun SettingsScreen( ) } ) { paddingValues -> - Column( + LazyColumn( Modifier .fillMaxSize() .padding(top = paddingValues.calculateTopPadding()) - .verticalScroll(rememberScrollState()) ) { - SettingsCategoryItem( - icon = Icons.Default.DisplaySettings, - text = stringResource(id = R.string.general_settings), - description = stringResource(id = R.string.general_settings_desc) - ) { - navigator.navigate(Screen.GENERAL_SETTINGS) + item { + SettingsCategoryItem( + icon = Icons.Default.DisplaySettings, + text = stringResource(id = R.string.general_settings), + description = stringResource(id = R.string.general_settings_desc) + ) { + navigator.navigate(Screen.GENERAL_SETTINGS) + } } - SettingsCategoryItem( - icon = Icons.Default.Palette, - text = stringResource(id = R.string.appearance_settings), - description = stringResource(id = R.string.appearance_settings_desc) - ) { - navigator.navigate(Screen.APPEARANCE_SETTINGS) + + item { + SettingsCategoryItem( + icon = Icons.Default.Palette, + text = stringResource(id = R.string.appearance_settings), + description = stringResource(id = R.string.appearance_settings_desc) + ) { + navigator.navigate(Screen.APPEARANCE_SETTINGS) + } } + + //todo Reader settings } } } \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/CheckboxWIthTitle.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/CheckboxWIthTitle.kt new file mode 100644 index 00000000..16e33546 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/CheckboxWIthTitle.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/ChipsWithTitle.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/ChipsWithTitle.kt new file mode 100644 index 00000000..96b1492c --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/ChipsWithTitle.kt @@ -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, + 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 + ) + } + } + }, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/ColorPickerWithTitle.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/ColorPickerWithTitle.kt new file mode 100644 index 00000000..927253b8 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/ColorPickerWithTitle.kt @@ -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, + 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 + ) + } + + } + } +} diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/SliderWithTitle.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/SliderWithTitle.kt new file mode 100644 index 00000000..8939be44 --- /dev/null +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/components/SliderWithTitle.kt @@ -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, + 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, + 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) + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/AppearanceSettings.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/AppearanceSettings.kt index f2872bc7..1dc549a1 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/AppearanceSettings.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/AppearanceSettings.kt @@ -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( - title = stringResource(id = R.string.dark_theme_option), - description = when (darkTheme) { - 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) + item { + ChipsWithTitle( + title = stringResource(id = R.string.dark_theme_option), + 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 + ) + }, + ) { + mainViewModel.onEvent( + MainEvent.OnChangeDarkTheme( + it.id + ) + ) } - ) { - viewModel.onEvent(AppearanceSettingsEvent.OnShowHideDarkThemeDialog) } - AppearanceSettingsThemeSwitcher(mainViewModel = mainViewModel) + + item { + AppearanceSettingsThemeSwitcher(mainViewModel = mainViewModel) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/components/AppearanceSettingsDarkThemeDialog.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/components/AppearanceSettingsDarkThemeDialog.kt deleted file mode 100644 index cb6e37a6..00000000 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/components/AppearanceSettingsDarkThemeDialog.kt +++ /dev/null @@ -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() - ) - ) - } - } - } - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcher.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcher.kt index e7dd133b..4a3eaaa1 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcher.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcher.kt @@ -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)) } } } diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcherItem.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcherItem.kt index 3d9a43cc..151d7240 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcherItem.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcherItem.kt @@ -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 ) diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/data/AppearanceSettingsEvent.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/data/AppearanceSettingsEvent.kt deleted file mode 100644 index 0ed54f4d..00000000 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/data/AppearanceSettingsEvent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.acclorite.books_history.presentation.screens.settings.nested.appearance.data - -sealed class AppearanceSettingsEvent() { - data object OnShowHideDarkThemeDialog : AppearanceSettingsEvent() -} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/data/AppearanceSettingsState.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/data/AppearanceSettingsState.kt deleted file mode 100644 index 8f396ddd..00000000 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/data/AppearanceSettingsState.kt +++ /dev/null @@ -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 -) diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/data/AppearanceSettingsViewModel.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/data/AppearanceSettingsViewModel.kt deleted file mode 100644 index 7ca62cdc..00000000 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/appearance/data/AppearanceSettingsViewModel.kt +++ /dev/null @@ -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 - ) - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/GeneralSettings.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/GeneralSettings.kt index 2ec94d70..46c68e2d 100644 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/GeneralSettings.kt +++ b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/GeneralSettings.kt @@ -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( - title = stringResource(id = R.string.language_option), - description = Constants.LANGUAGES.find { it.first == language }?.second - ?: return@Column - ) { - viewModel.onEvent(GeneralSettingsEvent.OnShowHideLanguageDialog) + item { + ChipsWithTitle( + title = stringResource(id = R.string.language_option), + chips = Constants.LANGUAGES.map { + ChipItem( + it.first, + it.second, + MaterialTheme.typography.labelLarge, + it.first == language + ) + } + ) { + mainViewModel.onEvent( + MainEvent.OnChangeLanguage( + it.id, + context + ) + ) + } } } } diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/components/GeneralSettingsLanguageDialog.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/components/GeneralSettingsLanguageDialog.kt deleted file mode 100644 index 1a920ab6..00000000 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/components/GeneralSettingsLanguageDialog.kt +++ /dev/null @@ -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 - ) - ) - } - } - } - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/data/GeneralSettingsEvent.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/data/GeneralSettingsEvent.kt deleted file mode 100644 index 2c1e86cc..00000000 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/data/GeneralSettingsEvent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.acclorite.books_history.presentation.screens.settings.nested.general.data - -sealed class GeneralSettingsEvent() { - data object OnShowHideLanguageDialog : GeneralSettingsEvent() -} \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/data/GeneralSettingsState.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/data/GeneralSettingsState.kt deleted file mode 100644 index e0772904..00000000 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/data/GeneralSettingsState.kt +++ /dev/null @@ -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 -) \ No newline at end of file diff --git a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/data/GeneralSettingsViewModel.kt b/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/data/GeneralSettingsViewModel.kt deleted file mode 100644 index 368f6ef6..00000000 --- a/app/src/main/java/com/acclorite/books_history/presentation/screens/settings/nested/general/data/GeneralSettingsViewModel.kt +++ /dev/null @@ -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 - ) - } - } - } - } - -} - - - - - - - - - - - diff --git a/app/src/main/java/com/acclorite/books_history/ui/Theme.kt b/app/src/main/java/com/acclorite/books_history/ui/Theme.kt index ec47cdb2..7a0ba602 100644 --- a/app/src/main/java/com/acclorite/books_history/ui/Theme.kt +++ b/app/src/main/java/com/acclorite/books_history/ui/Theme.kt @@ -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 } diff --git a/app/src/main/java/com/acclorite/books_history/ui/Typography.kt b/app/src/main/java/com/acclorite/books_history/ui/Typography.kt index 50caf5a5..5e16a3e4 100644 --- a/app/src/main/java/com/acclorite/books_history/ui/Typography.kt +++ b/app/src/main/java/com/acclorite/books_history/ui/Typography.kt @@ -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, diff --git a/app/src/main/java/com/acclorite/books_history/util/Constants.kt b/app/src/main/java/com/acclorite/books_history/util/Constants.kt index 80922c76..755b6f59 100644 --- a/app/src/main/java/com/acclorite/books_history/util/Constants.kt +++ b/app/src/main/java/com/acclorite/books_history/util/Constants.kt @@ -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), diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 2e7059c4..c7ca581f 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -51,6 +51,12 @@ Немає опису. + + Файл не знайдено. Будь ласка, перевірте чи файл цієї книги все ще існує. + + + Щось пішло не так. Будь ласка, перевірте чи файл цієї книги не пошкоджений. + @@ -64,6 +70,7 @@ %1$s обрано Знайти %1$s… Ви прочитали %1$s цієї книги. + %1$s прочитано Відмінити @@ -77,6 +84,9 @@ Почати Продовжити Ніколи + Назад + Перемістити цю книгу в \"Прочитано\" + Назад в Бібліотеку файли @@ -96,23 +106,44 @@ Прочитано Планую Кинуто + Головні + Кольори Головні Мова застосунку Зовнішній вигляд Тема застосунку, темна тема + Шрифт + Текст Мова застосунку Темна тема Тема застосунку + Сімейство шрифтів + Стиль шрифту + Розмір шрифту + Висота рядка + Висота абзацу + Абзацний відступ + Колір фону + Колір шрифту + + + Червоний + Зелений + Синій Вимкнено Ввімкнено Тема системи + + Похилий + Нормальний + Хаос Нептун @@ -147,4 +178,15 @@ Розмір файлу Невідомо + + Завантаження… + Приємного читання! + + Дякуємо за те, що прочитали \"%1$s\" з нами. + Ми сподіваємось, що вам сподобалася ця книга! + + + + За замовчуванням + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ac13fdfe..707607fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,6 +52,12 @@ No description. + + File was not found. Please check whether this book\'s file still exist. + + + Something went wrong. Please check whether this book\'s file is not corrupted. + @@ -65,6 +71,7 @@ %1$s selected Search %1$s… You read %1$s of this book. + %1$s read Cancel @@ -78,6 +85,9 @@ Start Continue Never + Go back + Move this book to \"Already Read\" + Back to Library files @@ -97,23 +107,44 @@ Already Read Planning Dropped + General + Colors General App language Appearance App theme, dark theme + Font + Text App language - Dark Theme + Dark theme App theme + Font family + Font style + Font size + Line height + Paragraph height + Paragraph indentation + Background color + Font color + + + Red + Green + Blue Disabled Enabled Follow system + + Italic + Normal + Chaos Neptune @@ -148,4 +179,15 @@ File size Unknown + + Loading… + Happy reading! + + Thank you for reading \"%1$s\" with us. + We hope you enjoyed this book! + + + + Default + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index e6496d73..5882a10a 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -6,6 +6,7 @@ @android:color/transparent @android:color/transparent + true