From 6806ac904c518e669b4460b5b07030cbec795f5a Mon Sep 17 00:00:00 2001 From: acclorite Date: Tue, 12 Mar 2024 17:06:08 +0200 Subject: [PATCH] 0.9.0 - New loading system(much faster) + New between screen transitions + New snackbar(vertical animation + visuals) + New selection toolbar(translate + dictionary options) in ReaderScreen + New top app bar in SettingsScreen and nested categories + New updating system in BookInfo(updating text, author and description of the book) + Tablet UI support(on wide screens NavigationBar -> NavigationRail) + New app icon + New error handling system(it won't let you add/update book if it's not valid) + New theme(+new names) + Small bugs fixed.. + General performance optimisation(still in progress). --- app/build.gradle.kts | 40 ++- app/src/main/AndroidManifest.xml | 26 +- app/src/main/app_icon-playstore.png | Bin 0 -> 32608 bytes .../main/app_icon_monochrome-playstore.png | Bin 0 -> 10096 bytes .../{MainActivity.kt => Activity.kt} | 116 +++++++-- .../book_story/data/di/RepositoryModule.kt | 8 +- .../book_story/data/local/dto/BookEntity.kt | 4 +- .../book_story/data/local/room/BookDao.kt | 6 +- .../book_story/data/mapper/BookMapperImpl.kt | 81 ------ .../data/mapper/{ => book}/BookMapper.kt | 2 +- .../data/mapper/book/BookMapperImpl.kt | 74 ++++++ .../mapper/{ => history}/HistoryMapper.kt | 5 +- .../mapper/{ => history}/HistoryMapperImpl.kt | 9 +- .../book_story/data/parser/TextParser.kt | 3 +- .../book_story/data/parser/TxtTextParser.kt | 34 --- .../data/parser/{ => epub}/EpubFileParser.kt | 6 +- .../data/parser/{ => epub}/EpubTextParser.kt | 23 +- .../data/parser/{ => pdf}/PdfFileParser.kt | 3 +- .../data/parser/{ => pdf}/PdfTextParser.kt | 29 ++- .../data/parser/{ => txt}/TxtFileParser.kt | 3 +- .../data/parser/txt/TxtTextParser.kt | 49 ++++ .../data/repository/BookRepositoryImpl.kt | 201 ++++++++++----- .../acclorite/book_story/domain/model/Book.kt | 4 +- .../book_story/domain/model/Category.kt | 3 + .../book_story/domain/model/History.kt | 2 +- ...NavigationBarItem.kt => NavigationItem.kt} | 2 +- .../book_story/domain/model/NullableBook.kt | 19 +- .../domain/repository/BookRepository.kt | 6 +- .../domain/use_case/GetBookFromFile.kt | 15 ++ .../domain/use_case/GetBooksById.kt | 12 + .../domain/use_case/GetBooksFromFiles.kt | 4 +- .../components/AnimatedTopAppBar.kt | 9 + .../presentation/components/CategoryTitle.kt | 3 + .../presentation/components/CustomCheckbox.kt | 3 + .../components/CustomDropDownMenuItem.kt | 3 + .../components/CustomSelectionContainer.kt | 236 ++++++++++++++++++ .../presentation/components/CustomSnackBar.kt | 49 ---- .../presentation/components/CustomSnackbar.kt | 100 ++++++++ .../presentation/components/GoBackButton.kt | 38 +++ .../presentation/components/MoreDropDown.kt | 13 +- .../BottomNavigationBar.kt | 20 +- .../BottomNavigationItem.kt | 6 +- .../CustomDialogWithContent.kt | 25 +- .../CustomDialogWithLazyColumn.kt | 26 +- .../SelectableDialogItem.kt | 5 +- .../CustomNavigationRail.kt | 90 +++++++ .../CustomNavigationRailItem.kt | 54 ++++ .../components/{ => is_messages}/IsEmpty.kt | 12 +- .../components/{ => is_messages}/IsError.kt | 11 +- .../presentation/data/ArithmeticOperations.kt | 21 +- .../presentation/data/MainViewModel.kt | 41 ++- .../presentation/{ => data}/Navigation.kt | 36 ++- .../screens/book_info/BookInfoScreen.kt | 199 +++++++++------ .../components/BookInfoBackground.kt | 5 +- .../components/BookInfoDescriptionSection.kt | 7 +- .../components/BookInfoInfoSection.kt | 6 +- .../components/BookInfoMoreDropDown.kt | 7 +- .../components/BookInfoStatisticSection.kt | 11 +- .../BookInfoChangeCoverBottomSheet.kt | 5 +- .../BookInfoChangeCoverBottomSheetItem.kt | 5 +- .../BookInfoDetailsBottomSheet.kt | 5 +- .../BookInfoDetailsBottomSheetItem.kt | 6 +- .../{ => dialog}/BookInfoDeleteDialog.kt | 9 +- .../{ => dialog}/BookInfoMoveDialog.kt | 11 +- .../screens/book_info/data/BookInfoEvent.kt | 17 +- .../screens/book_info/data/BookInfoState.kt | 1 + .../book_info/data/BookInfoViewModel.kt | 157 +++++++++++- .../screens/browse/BrowseScreen.kt | 39 +-- .../browse/components/BrowseFIleItem.kt | 3 + .../BrowseStoragePermissionDialog.kt | 5 +- .../{ => adding_dialog}/BrowseAddingDialog.kt | 28 ++- .../BrowseAddingDialogItem.kt | 27 +- .../screens/browse/data/BrowseEvent.kt | 2 +- .../screens/browse/data/BrowseViewModel.kt | 56 ++--- .../screens/history/HistoryScreen.kt | 57 +++-- .../HistoryDeleteWholeHistoryDialog.kt | 14 +- .../screens/history/components/HistoryItem.kt | 20 +- .../screens/history/data/HistoryEvent.kt | 3 +- .../screens/history/data/HistoryViewModel.kt | 37 ++- .../screens/library/LibraryScreen.kt | 56 ++--- .../library/components/LibraryBookItem.kt | 60 ++--- .../library/components/LibraryTabRow.kt | 11 +- .../{ => dialog}/LibraryDeleteDialog.kt | 7 +- .../{ => dialog}/LibraryMoveDialog.kt | 9 +- .../screens/library/data/LibraryViewModel.kt | 15 +- .../screens/reader/ReaderScreen.kt | 138 ++++++---- .../reader/components/ReaderEndItem.kt | 31 ++- .../reader/components/ReaderStartItem.kt | 12 +- .../{ => app_bar}/ReaderBottomBar.kt | 7 +- .../components/{ => app_bar}/ReaderTopBar.kt | 30 +-- .../ReaderSettingsBottomSheet.kt | 9 +- .../ReaderSettingsBottomSheetTabRow.kt | 5 +- .../screens/reader/data/ReaderEvent.kt | 16 +- .../screens/reader/data/ReaderState.kt | 3 + .../screens/reader/data/ReaderViewModel.kt | 126 ++++++---- .../screens/settings/SettingsScreen.kt | 71 +++--- .../settings/components/CheckboxWIthTitle.kt | 3 + .../settings/components/ChipsWithTitle.kt | 3 + .../components/ColorPickerWithTitle.kt | 3 + .../components/SettingsCategoryItem.kt | 10 +- .../settings/components/SliderWithTitle.kt | 3 + .../nested/appearance/AppearanceSettings.kt | 121 ++++++--- .../AppearanceSettingsThemeSwitcher.kt | 5 +- .../AppearanceSettingsThemeSwitcherItem.kt | 5 +- .../nested/general/GeneralSettings.kt | 58 ++--- .../settings/nested/reader/ReaderSettings.kt | 217 ++++++++++++++++ .../ua/acclorite/book_story/ui/Transitions.kt | 10 +- .../ua/acclorite/book_story/util/UIText.kt | 6 +- .../main/res/drawable/app_icon_colored.png | Bin 14963 -> 29654 bytes .../main/res/drawable/app_icon_monochrome.png | Bin 0 -> 5996 bytes app/src/main/res/drawable/splash_icon.xml | 4 +- .../app_icon.xml | 4 +- .../app_icon_round.xml | 4 +- app/src/main/res/mipmap-hdpi/app_icon.webp | Bin 1720 -> 1546 bytes .../res/mipmap-hdpi/app_icon_foreground.webp | Bin 2078 -> 2424 bytes .../res/mipmap-hdpi/app_icon_monochrome.webp | Bin 262 -> 362 bytes .../main/res/mipmap-hdpi/app_icon_round.webp | Bin 3072 -> 2990 bytes app/src/main/res/mipmap-mdpi/app_icon.webp | Bin 1108 -> 1010 bytes .../res/mipmap-mdpi/app_icon_foreground.webp | Bin 1482 -> 1708 bytes .../res/mipmap-mdpi/app_icon_monochrome.webp | Bin 200 -> 252 bytes .../main/res/mipmap-mdpi/app_icon_round.webp | Bin 2004 -> 1898 bytes app/src/main/res/mipmap-xhdpi/app_icon.webp | Bin 2344 -> 2156 bytes .../res/mipmap-xhdpi/app_icon_foreground.webp | Bin 2792 -> 3280 bytes .../res/mipmap-xhdpi/app_icon_monochrome.webp | Bin 340 -> 464 bytes .../main/res/mipmap-xhdpi/app_icon_round.webp | Bin 4598 -> 4422 bytes app/src/main/res/mipmap-xxhdpi/app_icon.webp | Bin 3574 -> 3358 bytes .../mipmap-xxhdpi/app_icon_foreground.webp | Bin 4206 -> 5080 bytes .../mipmap-xxhdpi/app_icon_monochrome.webp | Bin 498 -> 702 bytes .../res/mipmap-xxhdpi/app_icon_round.webp | Bin 7074 -> 6858 bytes app/src/main/res/mipmap-xxxhdpi/app_icon.webp | Bin 4702 -> 4422 bytes .../mipmap-xxxhdpi/app_icon_foreground.webp | Bin 5092 -> 6634 bytes .../mipmap-xxxhdpi/app_icon_monochrome.webp | Bin 656 -> 1006 bytes .../res/mipmap-xxxhdpi/app_icon_round.webp | Bin 9634 -> 9394 bytes app/src/main/res/values-uk/strings.xml | 55 +++- app/src/main/res/values/strings.xml | 41 ++- app/src/main/res/values/themes.xml | 1 + 136 files changed, 2580 insertions(+), 911 deletions(-) create mode 100644 app/src/main/app_icon-playstore.png create mode 100644 app/src/main/app_icon_monochrome-playstore.png rename app/src/main/java/ua/acclorite/book_story/{MainActivity.kt => Activity.kt} (58%) delete mode 100644 app/src/main/java/ua/acclorite/book_story/data/mapper/BookMapperImpl.kt rename app/src/main/java/ua/acclorite/book_story/data/mapper/{ => book}/BookMapper.kt (82%) create mode 100644 app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt rename app/src/main/java/ua/acclorite/book_story/data/mapper/{ => history}/HistoryMapper.kt (54%) rename app/src/main/java/ua/acclorite/book_story/data/mapper/{ => history}/HistoryMapperImpl.kt (66%) delete mode 100644 app/src/main/java/ua/acclorite/book_story/data/parser/TxtTextParser.kt rename app/src/main/java/ua/acclorite/book_story/data/parser/{ => epub}/EpubFileParser.kt (89%) rename app/src/main/java/ua/acclorite/book_story/data/parser/{ => epub}/EpubTextParser.kt (66%) rename app/src/main/java/ua/acclorite/book_story/data/parser/{ => pdf}/PdfFileParser.kt (94%) rename app/src/main/java/ua/acclorite/book_story/data/parser/{ => pdf}/PdfTextParser.kt (62%) rename app/src/main/java/ua/acclorite/book_story/data/parser/{ => txt}/TxtFileParser.kt (91%) create mode 100644 app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtTextParser.kt rename app/src/main/java/ua/acclorite/book_story/domain/model/{NavigationBarItem.kt => NavigationItem.kt} (88%) create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBookFromFile.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBooksById.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSelectionContainer.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSnackBar.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSnackbar.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/GoBackButton.kt rename app/src/main/java/ua/acclorite/book_story/presentation/components/{ => bottom_navigation_bar}/BottomNavigationBar.kt (80%) rename app/src/main/java/ua/acclorite/book_story/presentation/components/{ => bottom_navigation_bar}/BottomNavigationItem.kt (91%) rename app/src/main/java/ua/acclorite/book_story/presentation/components/{ => custom_dialog}/CustomDialogWithContent.kt (87%) rename app/src/main/java/ua/acclorite/book_story/presentation/components/{ => custom_dialog}/CustomDialogWithLazyColumn.kt (88%) rename app/src/main/java/ua/acclorite/book_story/presentation/components/{ => custom_dialog}/SelectableDialogItem.kt (91%) create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/custom_navigation_rail/CustomNavigationRail.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/custom_navigation_rail/CustomNavigationRailItem.kt rename app/src/main/java/ua/acclorite/book_story/presentation/components/{ => is_messages}/IsEmpty.kt (87%) rename app/src/main/java/ua/acclorite/book_story/presentation/components/{ => is_messages}/IsError.kt (85%) rename app/src/main/java/ua/acclorite/book_story/presentation/{ => data}/Navigation.kt (81%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/{ => change_cover_bottom_sheet}/BookInfoChangeCoverBottomSheet.kt (97%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/{ => change_cover_bottom_sheet}/BookInfoChangeCoverBottomSheetItem.kt (96%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/{ => details_bottom_sheet}/BookInfoDetailsBottomSheet.kt (97%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/{ => details_bottom_sheet}/BookInfoDetailsBottomSheetItem.kt (96%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/{ => dialog}/BookInfoDeleteDialog.kt (91%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/{ => dialog}/BookInfoMoveDialog.kt (90%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/{ => adding_dialog}/BrowseAddingDialog.kt (77%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/{ => adding_dialog}/BrowseAddingDialogItem.kt (81%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/{ => dialog}/LibraryDeleteDialog.kt (92%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/{ => dialog}/LibraryMoveDialog.kt (90%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/{ => app_bar}/ReaderBottomBar.kt (96%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/{ => app_bar}/ReaderTopBar.kt (82%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/{ => settings_bottom_sheet}/ReaderSettingsBottomSheet.kt (98%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/{ => settings_bottom_sheet}/ReaderSettingsBottomSheetTabRow.kt (96%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/{ => theme_switcher}/AppearanceSettingsThemeSwitcher.kt (97%) rename app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/{ => theme_switcher}/AppearanceSettingsThemeSwitcherItem.kt (98%) create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt create mode 100644 app/src/main/res/drawable/app_icon_monochrome.png rename app/src/main/res/{mipmap-anydpi => mipmap-anydpi-v26}/app_icon.xml (60%) rename app/src/main/res/{mipmap-anydpi => mipmap-anydpi-v26}/app_icon_round.xml (60%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a9dc1af8..4b5e05db 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,10 +2,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("com.google.devtools.ksp") - id("com.google.dagger.hilt.android") - kotlin("kapt") - id("kotlin-parcelize") } @@ -18,7 +15,7 @@ android { minSdk = 26 targetSdk = 34 versionCode = 1 - versionName = "0.8.0" + versionName = "0.9.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -62,35 +59,35 @@ android { } dependencies { - val composeVersion = "1.5.7" // Default implementation("androidx.core:core-ktx:1.12.0") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.activity:activity-compose:1.8.2") + // Compose BOM libraries implementation(platform("androidx.compose:compose-bom:2024.01.00")) - implementation("androidx.compose.ui:ui:$composeVersion") - implementation("androidx.compose.ui:ui-graphics:$composeVersion") - implementation("androidx.compose.ui:ui-tooling-preview:$composeVersion") - implementation("androidx.compose.ui:ui-android:1.6.0") - implementation("androidx.compose.material3:material3:1.1.2") - implementation("androidx.compose.material:material:1.6.0") + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-graphics") + implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.compose.ui:ui-android") + implementation("androidx.compose.material3:material3") + implementation("androidx.compose.material3:material3-window-size-class") + implementation("androidx.compose.material:material") // Unit tests testImplementation("androidx.test:core:latest.release") testImplementation("junit:junit:latest.release") testImplementation("androidx.arch.core:core-testing:latest.release") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:latest.release") - testImplementation("com.google.truth:truth:1.2.0") + testImplementation("com.google.truth:truth:1.4.2") testImplementation("androidx.test.ext:truth:1.5.0") testImplementation("com.squareup.okhttp3:mockwebserver:latest.release") testImplementation("io.mockk:mockk:latest.release") - debugImplementation("androidx.compose.ui:ui-test-manifest:1.5.4") + debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.3") // Instrumentation tests androidTestImplementation("com.google.dagger:hilt-android-testing:latest.release") - kaptAndroidTest("com.google.dagger:hilt-android-compiler:latest.release") androidTestImplementation("junit:junit:latest.release") androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:latest.release") androidTestImplementation("androidx.arch.core:core-testing:latest.release") @@ -102,17 +99,16 @@ dependencies { androidTestImplementation("androidx.test:runner:latest.release") // All dependencies - implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2") - implementation("androidx.compose.material:material-icons-extended:1.5.4") - implementation("androidx.activity:activity-compose:1.8.2") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") + implementation("androidx.compose.material:material-icons-extended") implementation("com.google.accompanist:accompanist-swiperefresh:0.24.2-alpha") // Dagger - Hilt - implementation("com.google.dagger:hilt-android:2.50") - kapt("com.google.dagger:hilt-android-compiler:2.50") + implementation("com.google.dagger:hilt-android:2.51") + ksp("com.google.dagger:hilt-android-compiler:2.51") implementation("com.google.dagger:hilt-compiler:latest.release") - kapt("androidx.hilt:hilt-compiler:latest.release") - implementation("androidx.hilt:hilt-navigation-compose:1.1.0") + ksp("androidx.hilt:hilt-compiler:latest.release") + implementation("androidx.hilt:hilt-navigation-compose:1.2.0") // Room implementation("androidx.room:room-runtime:2.6.1") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b438d2f9..7d1dab8b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,21 @@ android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" /> + + + + + + + + + + + + + + + - - - - \ No newline at end of file diff --git a/app/src/main/app_icon-playstore.png b/app/src/main/app_icon-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..62d5d399f1880a39747b0bf3d9e54bd9fcc4a21f GIT binary patch literal 32608 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 Uvl}Xv0W(1|Qi_iXB%b;GAAe@LBme*a literal 0 HcmV?d00001 diff --git a/app/src/main/app_icon_monochrome-playstore.png b/app/src/main/app_icon_monochrome-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..26220dc80fd280285e2263e15fd0f8c84b13d072 GIT binary patch literal 10096 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 E05nL0@Bjb+ literal 0 HcmV?d00001 diff --git a/app/src/main/java/ua/acclorite/book_story/MainActivity.kt b/app/src/main/java/ua/acclorite/book_story/Activity.kt similarity index 58% rename from app/src/main/java/ua/acclorite/book_story/MainActivity.kt rename to app/src/main/java/ua/acclorite/book_story/Activity.kt index e33f3564..b8ccfa15 100644 --- a/app/src/main/java/ua/acclorite/book_story/MainActivity.kt +++ b/app/src/main/java/ua/acclorite/book_story/Activity.kt @@ -1,67 +1,104 @@ package ua.acclorite.book_story +import android.annotation.SuppressLint import android.content.res.Configuration +import android.database.CursorWindow import android.os.Bundle +import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.slideInHorizontally import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import dagger.hilt.android.AndroidEntryPoint import ua.acclorite.book_story.domain.model.Book -import ua.acclorite.book_story.presentation.NavigationHost -import ua.acclorite.book_story.presentation.Screen -import ua.acclorite.book_story.presentation.components.BottomNavigationBar +import ua.acclorite.book_story.presentation.components.bottom_navigation_bar.BottomNavigationBar +import ua.acclorite.book_story.presentation.components.custom_navigation_rail.CustomNavigationRail import ua.acclorite.book_story.presentation.data.MainEvent import ua.acclorite.book_story.presentation.data.MainViewModel +import ua.acclorite.book_story.presentation.data.NavigationHost +import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.presentation.screens.book_info.BookInfoScreen import ua.acclorite.book_story.presentation.screens.browse.BrowseScreen +import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel import ua.acclorite.book_story.presentation.screens.history.HistoryScreen +import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel import ua.acclorite.book_story.presentation.screens.library.LibraryScreen +import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel import ua.acclorite.book_story.presentation.screens.reader.ReaderScreen import ua.acclorite.book_story.presentation.screens.settings.SettingsScreen import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.AppearanceSettings import ua.acclorite.book_story.presentation.screens.settings.nested.general.GeneralSettings +import ua.acclorite.book_story.presentation.screens.settings.nested.reader.ReaderSettings import ua.acclorite.book_story.ui.BooksHistoryResurrectionTheme +import ua.acclorite.book_story.ui.DarkTheme +import ua.acclorite.book_story.ui.Theme import ua.acclorite.book_story.ui.Transitions import ua.acclorite.book_story.ui.isDark +import java.lang.reflect.Field + +@SuppressLint("DiscouragedPrivateApi") +@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @AndroidEntryPoint -class MainActivity : AppCompatActivity() { +class Activity : ComponentActivity() { private val mainViewModel: MainViewModel by viewModels() + private val libraryViewModel: LibraryViewModel by viewModels() + private val historyViewModel: HistoryViewModel by viewModels() + private val browseViewModel: BrowseViewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - mainViewModel.init(this) + // New Cursor size for Room + try { + val field: Field = CursorWindow::class.java.getDeclaredField("sCursorWindowSize") + field.isAccessible = true + field.set(null, 100 * 1024 * 1024) + } catch (e: Exception) { + e.printStackTrace() + } + + // Initializing all variables + mainViewModel.init( + activity = this, + libraryViewModel, + ) + + // Splash screen installSplashScreen().apply { setKeepOnScreenCondition { !mainViewModel.isReady.value } } + // Edge to edge enableEdgeToEdge() WindowCompat.setDecorFitsSystemWindows(window, false) setContent { + val windowClass = calculateWindowSizeClass(activity = this) val updating = mainViewModel.updating.collectAsState().value - val theme = mainViewModel.theme.collectAsState().value!! - val darkTheme = mainViewModel.darkTheme.collectAsState().value!! + + val theme = mainViewModel.theme.collectAsState().value ?: Theme.BLUE + val darkTheme = + mainViewModel.darkTheme.collectAsState().value ?: DarkTheme.FOLLOW_SYSTEM + val tabletUI = windowClass.widthSizeClass != WindowWidthSizeClass.Compact BooksHistoryResurrectionTheme( theme = theme, @@ -70,28 +107,36 @@ class MainActivity : AppCompatActivity() { if (!updating) { NavigationHost(startScreen = Screen.LIBRARY) { val currentScreen by this.getCurrentScreen().collectAsState() + AnimatedVisibility( visible = currentScreen == Screen.LIBRARY || currentScreen == Screen.HISTORY || currentScreen == Screen.BROWSE, - enter = fadeIn(tween(200)) + - slideInHorizontally(tween(200)) { it / 15 }, + enter = Transitions.BackSlidingTransitionIn, exit = Transitions.SlidingTransitionOut ) { Scaffold( bottomBar = { - BottomNavigationBar(navigator = this@NavigationHost) + if (!tabletUI) { + BottomNavigationBar(navigator = this@NavigationHost) + } }, containerColor = MaterialTheme.colorScheme.surface ) { Box( modifier = Modifier .fillMaxSize() - .padding(bottom = it.calculateBottomPadding()) + .padding( + start = if (tabletUI) 80.dp else 0.dp, + bottom = it.calculateBottomPadding() + ) ) { composable(screen = Screen.LIBRARY) { @Suppress("UNCHECKED_CAST") LibraryScreen( + viewModel = libraryViewModel, + historyViewModel = historyViewModel, + browseViewModel = browseViewModel, navigator = this@NavigationHost, addedBooks = retrieveArgument("added_books") as? List ?: emptyList() @@ -99,13 +144,25 @@ class MainActivity : AppCompatActivity() { } composable(screen = Screen.HISTORY) { - HistoryScreen(navigator = this@NavigationHost) + HistoryScreen( + viewModel = historyViewModel, + libraryViewModel = libraryViewModel, + navigator = this@NavigationHost + ) } composable(screen = Screen.BROWSE) { - BrowseScreen(navigator = this@NavigationHost) + BrowseScreen( + viewModel = browseViewModel, + libraryViewModel = libraryViewModel, + navigator = this@NavigationHost + ) } } + + if (tabletUI) { + CustomNavigationRail(navigator = this@NavigationHost) + } } } @@ -116,6 +173,9 @@ class MainActivity : AppCompatActivity() { exitAnim = Transitions.SlidingTransitionOut ) { BookInfoScreen( + libraryViewModel = libraryViewModel, + browseViewModel = browseViewModel, + historyViewModel = historyViewModel, navigator = this@NavigationHost ) } @@ -125,6 +185,9 @@ class MainActivity : AppCompatActivity() { exitAnim = Transitions.SlidingTransitionOut ) { ReaderScreen( + mainViewModel = mainViewModel, + libraryViewModel = libraryViewModel, + historyViewModel = historyViewModel, navigator = this@NavigationHost ) } @@ -147,6 +210,7 @@ class MainActivity : AppCompatActivity() { exitAnim = Transitions.SlidingTransitionOut ) { GeneralSettings( + mainViewModel = mainViewModel, navigator = this@NavigationHost ) } @@ -156,9 +220,25 @@ class MainActivity : AppCompatActivity() { exitAnim = Transitions.SlidingTransitionOut ) { AppearanceSettings( + mainViewModel = mainViewModel, navigator = this@NavigationHost ) } + composable( + screen = Screen.READER_SETTINGS, + enterAnim = Transitions.SlidingTransitionIn, + exitAnim = Transitions.SlidingTransitionOut + ) { + ReaderSettings( + mainViewModel = mainViewModel, + navigator = this@NavigationHost + ) + } + +// Start screen (later) +// composable(screen = Screen.START) { +// StartScreen() +// } } } else { Box( diff --git a/app/src/main/java/ua/acclorite/book_story/data/di/RepositoryModule.kt b/app/src/main/java/ua/acclorite/book_story/data/di/RepositoryModule.kt index a129eccf..bf178666 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/di/RepositoryModule.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/di/RepositoryModule.kt @@ -6,10 +6,10 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import ua.acclorite.book_story.data.local.data_store.DataStore import ua.acclorite.book_story.data.local.data_store.DataStoreImpl -import ua.acclorite.book_story.data.mapper.BookMapper -import ua.acclorite.book_story.data.mapper.BookMapperImpl -import ua.acclorite.book_story.data.mapper.HistoryMapper -import ua.acclorite.book_story.data.mapper.HistoryMapperImpl +import ua.acclorite.book_story.data.mapper.book.BookMapper +import ua.acclorite.book_story.data.mapper.book.BookMapperImpl +import ua.acclorite.book_story.data.mapper.history.HistoryMapper +import ua.acclorite.book_story.data.mapper.history.HistoryMapperImpl import ua.acclorite.book_story.data.repository.BookRepositoryImpl import ua.acclorite.book_story.domain.repository.BookRepository import javax.inject.Singleton diff --git a/app/src/main/java/ua/acclorite/book_story/data/local/dto/BookEntity.kt b/app/src/main/java/ua/acclorite/book_story/data/local/dto/BookEntity.kt index 08addf36..719c5dc2 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/local/dto/BookEntity.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/local/dto/BookEntity.kt @@ -8,9 +8,11 @@ import ua.acclorite.book_story.domain.model.Category data class BookEntity( @PrimaryKey(true) val id: Int? = null, val title: String, + val author: String?, + val description: String?, + val text: String, val filePath: String, val progress: Float, - var lastOpened: Long? = null, val image: ByteArray = byteArrayOf(), val category: Category ) { diff --git a/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt b/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt index eb3f9fec..e110fb27 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt @@ -19,8 +19,7 @@ interface BookDao { @Query( """ - SELECT * - FROM bookentity + SELECT * FROM bookentity WHERE LOWER(title) LIKE '%' || LOWER(:query) || '%' """ ) @@ -31,6 +30,9 @@ interface BookDao { ) suspend fun findBookById(id: Int): BookEntity + @Query("SELECT * FROM bookentity WHERE id IN (:ids)") + suspend fun findBooksById(ids: List): List + @Delete suspend fun deleteBooks(books: List) diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/BookMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/BookMapperImpl.kt deleted file mode 100644 index f72eb061..00000000 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/BookMapperImpl.kt +++ /dev/null @@ -1,81 +0,0 @@ -package ua.acclorite.book_story.data.mapper - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import ua.acclorite.book_story.R -import ua.acclorite.book_story.data.local.dto.BookEntity -import ua.acclorite.book_story.data.parser.EpubFileParser -import ua.acclorite.book_story.data.parser.PdfFileParser -import ua.acclorite.book_story.data.parser.TxtFileParser -import ua.acclorite.book_story.domain.model.Book -import ua.acclorite.book_story.util.UIText -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.File -import javax.inject.Inject - -class BookMapperImpl @Inject constructor( - private val txtFileParser: TxtFileParser, - private val pdfFileParser: PdfFileParser, - private val epubFileParser: EpubFileParser -) : BookMapper { - override suspend fun toBookEntity(book: Book): BookEntity { - val stream = ByteArrayOutputStream() - book.coverImage?.compress(Bitmap.CompressFormat.JPEG, 30, stream) - - return BookEntity( - id = book.id, - title = book.title, - filePath = book.filePath, - progress = book.progress, - lastOpened = book.lastOpened, - image = stream.toByteArray(), - category = book.category - ) - } - - override suspend fun toBook(bookEntity: BookEntity): Book { - val file = File(bookEntity.filePath) - val inputStream = ByteArrayInputStream(bookEntity.image) - val image = BitmapFactory.decodeStream(inputStream) - - val parsedBook = if (file.name.endsWith(".txt")) { - txtFileParser.parse(file) - } else if (file.name.endsWith(".pdf")) { - pdfFileParser.parse(file) - } else if (file.name.endsWith(".epub")) { - epubFileParser.parse(file) - } else { - null - } - - return if (parsedBook != null) - Book( - id = bookEntity.id, - title = bookEntity.title, - author = parsedBook.author, - description = parsedBook.description, - text = emptyList(), - progress = bookEntity.progress, - file = file, - filePath = bookEntity.filePath, - lastOpened = bookEntity.lastOpened, - category = bookEntity.category, - coverImage = if (bookEntity.image.isNotEmpty()) image else null - ) - else - Book( - id = bookEntity.id, - title = bookEntity.title, - author = UIText.StringResource(R.string.unknown_author), - text = emptyList(), - description = null, - progress = bookEntity.progress, - file = null, - filePath = bookEntity.filePath, - lastOpened = bookEntity.lastOpened, - category = bookEntity.category, - coverImage = if (bookEntity.image.isNotEmpty()) image else null - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/BookMapper.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapper.kt similarity index 82% rename from app/src/main/java/ua/acclorite/book_story/data/mapper/BookMapper.kt rename to app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapper.kt index e3fe7bce..358807a3 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/BookMapper.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapper.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.data.mapper +package ua.acclorite.book_story.data.mapper.book import ua.acclorite.book_story.data.local.dto.BookEntity import ua.acclorite.book_story.domain.model.Book diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt new file mode 100644 index 00000000..f314e27d --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt @@ -0,0 +1,74 @@ +package ua.acclorite.book_story.data.mapper.book + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.os.Build +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.asImageBitmap +import ua.acclorite.book_story.R +import ua.acclorite.book_story.data.local.dto.BookEntity +import ua.acclorite.book_story.domain.model.Book +import ua.acclorite.book_story.domain.model.StringWithId +import ua.acclorite.book_story.util.UIText +import java.io.ByteArrayOutputStream +import java.io.File +import javax.inject.Inject + +class BookMapperImpl @Inject constructor() : BookMapper { + override suspend fun toBookEntity(book: Book): BookEntity { + val legacyAPI = Build.VERSION.SDK_INT < Build.VERSION_CODES.R + + val stream = ByteArrayOutputStream() + book.coverImage?.asAndroidBitmap()?.compress( + if (legacyAPI) Bitmap.CompressFormat.WEBP + else Bitmap.CompressFormat.WEBP_LOSSLESS, + if (legacyAPI) 30 else 100, + stream + ) + + val text = book.text.joinToString("\n") { + it.line.trim() + } + + return BookEntity( + id = book.id, + title = book.title, + filePath = book.filePath, + progress = book.progress, + author = book.author.string, + text = text, + description = book.description, + image = stream.toByteArray(), + category = book.category + ) + } + + override suspend fun toBook(bookEntity: BookEntity): Book { + val file = File(bookEntity.filePath) + var image: ImageBitmap? = null + + if (bookEntity.image.isNotEmpty()) { + image = BitmapFactory + .decodeByteArray(bookEntity.image, 0, bookEntity.image.size) + .asImageBitmap() + image.prepareToDraw() + } + + return Book( + id = bookEntity.id, + title = bookEntity.title, + author = bookEntity.author?.let { UIText.StringValue(it) } ?: UIText.StringResource( + R.string.unknown_author + ), + description = bookEntity.description, + progress = bookEntity.progress, + file = if (file.exists()) file else null, + text = bookEntity.text.split("\n").map { StringWithId(it) }, + filePath = bookEntity.filePath, + lastOpened = null, + category = bookEntity.category, + coverImage = image + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/HistoryMapper.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/history/HistoryMapper.kt similarity index 54% rename from app/src/main/java/ua/acclorite/book_story/data/mapper/HistoryMapper.kt rename to app/src/main/java/ua/acclorite/book_story/data/mapper/history/HistoryMapper.kt index 0648b0d0..2d607770 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/HistoryMapper.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/history/HistoryMapper.kt @@ -1,11 +1,10 @@ -package ua.acclorite.book_story.data.mapper +package ua.acclorite.book_story.data.mapper.history import ua.acclorite.book_story.data.local.dto.HistoryEntity -import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.History interface HistoryMapper { suspend fun toHistoryEntity(history: History): HistoryEntity - suspend fun toHistory(historyEntity: HistoryEntity, book: Book): History + suspend fun toHistory(historyEntity: HistoryEntity): History } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/HistoryMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/history/HistoryMapperImpl.kt similarity index 66% rename from app/src/main/java/ua/acclorite/book_story/data/mapper/HistoryMapperImpl.kt rename to app/src/main/java/ua/acclorite/book_story/data/mapper/history/HistoryMapperImpl.kt index 81c8ef76..ff6292f8 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/HistoryMapperImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/history/HistoryMapperImpl.kt @@ -1,7 +1,6 @@ -package ua.acclorite.book_story.data.mapper +package ua.acclorite.book_story.data.mapper.history import ua.acclorite.book_story.data.local.dto.HistoryEntity -import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.History import javax.inject.Inject @@ -9,15 +8,15 @@ class HistoryMapperImpl @Inject constructor() : HistoryMapper { override suspend fun toHistoryEntity(history: History): HistoryEntity { return HistoryEntity( id = history.id, - bookId = history.book.id ?: 0, + bookId = history.bookId, time = history.time ) } - override suspend fun toHistory(historyEntity: HistoryEntity, book: Book): History { + override suspend fun toHistory(historyEntity: HistoryEntity): History { return History( historyEntity.id, - book = book, + bookId = historyEntity.bookId, time = historyEntity.time ) } diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/TextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/TextParser.kt index 50be3cec..fcee309e 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/TextParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/TextParser.kt @@ -1,8 +1,9 @@ package ua.acclorite.book_story.data.parser import ua.acclorite.book_story.domain.model.StringWithId +import ua.acclorite.book_story.util.Resource import java.io.File interface TextParser { - suspend fun parse(file: File): List + suspend fun parse(file: File): Resource> } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/TxtTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/TxtTextParser.kt deleted file mode 100644 index 1964463a..00000000 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/TxtTextParser.kt +++ /dev/null @@ -1,34 +0,0 @@ -package ua.acclorite.book_story.data.parser - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import ua.acclorite.book_story.domain.model.StringWithId -import java.io.BufferedReader -import java.io.File -import java.io.FileReader -import javax.inject.Inject - -class TxtTextParser @Inject constructor() : TextParser { - - override suspend fun parse(file: File): List { - if (!file.name.endsWith(".txt")) { - return emptyList() - } - - return try { - val formattedLines = mutableListOf() - - withContext(Dispatchers.IO) { - BufferedReader(FileReader(file)).forEachLine { line -> - formattedLines.add( - StringWithId(line) - ) - } - } - - formattedLines - } catch (e: Exception) { - emptyList() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/EpubFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt similarity index 89% rename from app/src/main/java/ua/acclorite/book_story/data/parser/EpubFileParser.kt rename to app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt index 3547b05a..482745c7 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/EpubFileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt @@ -1,9 +1,11 @@ -package ua.acclorite.book_story.data.parser +package ua.acclorite.book_story.data.parser.epub import android.graphics.BitmapFactory +import androidx.compose.ui.graphics.asImageBitmap import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import nl.siegmann.epublib.epub.EpubReader +import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.Category import ua.acclorite.book_story.util.UIText @@ -51,7 +53,7 @@ class EpubFileParser @Inject constructor() : FileParser { filePath = file.path, lastOpened = null, category = Category.entries[0], - coverImage = coverImage + coverImage = coverImage?.asImageBitmap() ) } catch (e: Exception) { return null diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/EpubTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt similarity index 66% rename from app/src/main/java/ua/acclorite/book_story/data/parser/EpubTextParser.kt rename to app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt index c8ee1158..35688701 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/EpubTextParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt @@ -1,10 +1,14 @@ -package ua.acclorite.book_story.data.parser +package ua.acclorite.book_story.data.parser.epub import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import nl.siegmann.epublib.epub.EpubReader import org.jsoup.Jsoup +import ua.acclorite.book_story.R +import ua.acclorite.book_story.data.parser.TextParser import ua.acclorite.book_story.domain.model.StringWithId +import ua.acclorite.book_story.util.Resource +import ua.acclorite.book_story.util.UIText import java.io.BufferedReader import java.io.File import java.io.FileInputStream @@ -14,9 +18,9 @@ import javax.inject.Inject class EpubTextParser @Inject constructor() : TextParser { - override suspend fun parse(file: File): List { + override suspend fun parse(file: File): Resource> { if (!file.name.endsWith(".epub")) { - return emptyList() + return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format)) } try { @@ -47,9 +51,18 @@ class EpubTextParser @Inject constructor() : TextParser { } } - return stringWithIds + if (stringWithIds.isEmpty()) { + return Resource.Error(UIText.StringResource(R.string.error_file_empty)) + } + + return Resource.Success(stringWithIds) } catch (e: Exception) { - return emptyList() + return Resource.Error( + UIText.StringResource( + R.string.error_query, + e.message?.take(40)?.trim() ?: "" + ) + ) } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/PdfFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt similarity index 94% rename from app/src/main/java/ua/acclorite/book_story/data/parser/PdfFileParser.kt rename to app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt index 36badf5c..5b894e1e 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/PdfFileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt @@ -1,9 +1,10 @@ -package ua.acclorite.book_story.data.parser +package ua.acclorite.book_story.data.parser.pdf import android.app.Application import com.tom_roush.pdfbox.android.PDFBoxResourceLoader import com.tom_roush.pdfbox.pdmodel.PDDocument import ua.acclorite.book_story.R +import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.Category import ua.acclorite.book_story.domain.model.StringWithId diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/PdfTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfTextParser.kt similarity index 62% rename from app/src/main/java/ua/acclorite/book_story/data/parser/PdfTextParser.kt rename to app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfTextParser.kt index 42391f12..19759293 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/PdfTextParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfTextParser.kt @@ -1,18 +1,22 @@ -package ua.acclorite.book_story.data.parser +package ua.acclorite.book_story.data.parser.pdf import android.app.Application import com.tom_roush.pdfbox.android.PDFBoxResourceLoader import com.tom_roush.pdfbox.pdmodel.PDDocument import com.tom_roush.pdfbox.text.PDFTextStripper +import ua.acclorite.book_story.R +import ua.acclorite.book_story.data.parser.TextParser import ua.acclorite.book_story.domain.model.StringWithId +import ua.acclorite.book_story.util.Resource +import ua.acclorite.book_story.util.UIText import java.io.File import javax.inject.Inject class PdfTextParser @Inject constructor(private val application: Application) : TextParser { - override suspend fun parse(file: File): List { + override suspend fun parse(file: File): Resource> { if (!file.name.endsWith(".pdf")) { - return emptyList() + return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format)) } try { @@ -24,6 +28,11 @@ class PdfTextParser @Inject constructor(private val application: Application) : val pdfStripper = PDFTextStripper() pdfStripper.paragraphStart = "_new_line_" + if (document.isEncrypted) { + document.close() + return Resource.Error(UIText.StringResource(R.string.error_file_encrypted)) + } + val oldText = pdfStripper.getText(document) .replace("\r", "") @@ -38,7 +47,7 @@ class PdfTextParser @Inject constructor(private val application: Application) : } val unformattedLines = text.split("${pdfStripper.paragraphStart}|\\n".toRegex()) - .filter { it.isNotEmpty() } + .filter { it.isNotBlank() } val lines = mutableListOf() unformattedLines.forEachIndexed { index, string -> @@ -52,10 +61,18 @@ class PdfTextParser @Inject constructor(private val application: Application) : stringWithIds.add(StringWithId(line.trim())) } - return stringWithIds + if (stringWithIds.isEmpty()) { + return Resource.Error(UIText.StringResource(R.string.error_file_empty)) + } + return Resource.Success(stringWithIds) } catch (e: Exception) { - return emptyList() + return Resource.Error( + UIText.StringResource( + R.string.error_query, + e.message?.take(40)?.trim() ?: "" + ) + ) } } } diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/TxtFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt similarity index 91% rename from app/src/main/java/ua/acclorite/book_story/data/parser/TxtFileParser.kt rename to app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt index bb7f0fe1..f2b9485d 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/TxtFileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt @@ -1,6 +1,7 @@ -package ua.acclorite.book_story.data.parser +package ua.acclorite.book_story.data.parser.txt import ua.acclorite.book_story.R +import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.Category import ua.acclorite.book_story.domain.model.StringWithId diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtTextParser.kt new file mode 100644 index 00000000..d1756dda --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtTextParser.kt @@ -0,0 +1,49 @@ +package ua.acclorite.book_story.data.parser.txt + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import ua.acclorite.book_story.R +import ua.acclorite.book_story.data.parser.TextParser +import ua.acclorite.book_story.domain.model.StringWithId +import ua.acclorite.book_story.util.Resource +import ua.acclorite.book_story.util.UIText +import java.io.BufferedReader +import java.io.File +import java.io.FileReader +import javax.inject.Inject + +class TxtTextParser @Inject constructor() : TextParser { + + override suspend fun parse(file: File): Resource> { + if (!file.name.endsWith(".txt")) { + return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format)) + } + + return try { + val formattedLines = mutableListOf() + + withContext(Dispatchers.IO) { + BufferedReader(FileReader(file)).forEachLine { line -> + if (line.isNotBlank()) { + formattedLines.add( + StringWithId(line) + ) + } + } + } + + if (formattedLines.isEmpty()) { + return Resource.Error(UIText.StringResource(R.string.error_file_empty)) + } + + Resource.Success(formattedLines) + } catch (e: Exception) { + Resource.Error( + UIText.StringResource( + R.string.error_query, + e.message?.take(40)?.trim() ?: "" + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt index 30a15dd3..915605c7 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt @@ -4,24 +4,27 @@ import android.os.Environment import androidx.datastore.preferences.core.Preferences import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import ua.acclorite.book_story.R import ua.acclorite.book_story.data.local.data_store.DataStore import ua.acclorite.book_story.data.local.dto.BookEntity import ua.acclorite.book_story.data.local.room.BookDao -import ua.acclorite.book_story.data.mapper.BookMapper -import ua.acclorite.book_story.data.mapper.HistoryMapper -import ua.acclorite.book_story.data.parser.EpubFileParser -import ua.acclorite.book_story.data.parser.EpubTextParser -import ua.acclorite.book_story.data.parser.PdfFileParser -import ua.acclorite.book_story.data.parser.PdfTextParser -import ua.acclorite.book_story.data.parser.TxtFileParser -import ua.acclorite.book_story.data.parser.TxtTextParser +import ua.acclorite.book_story.data.mapper.book.BookMapper +import ua.acclorite.book_story.data.mapper.history.HistoryMapper +import ua.acclorite.book_story.data.parser.epub.EpubFileParser +import ua.acclorite.book_story.data.parser.epub.EpubTextParser +import ua.acclorite.book_story.data.parser.pdf.PdfFileParser +import ua.acclorite.book_story.data.parser.pdf.PdfTextParser +import ua.acclorite.book_story.data.parser.txt.TxtFileParser +import ua.acclorite.book_story.data.parser.txt.TxtTextParser import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.History import ua.acclorite.book_story.domain.model.NullableBook import ua.acclorite.book_story.domain.model.StringWithId import ua.acclorite.book_story.domain.repository.BookRepository +import ua.acclorite.book_story.presentation.data.calculateFamiliarity import ua.acclorite.book_story.util.Constants import ua.acclorite.book_story.util.Resource +import ua.acclorite.book_story.util.UIText import java.io.File import javax.inject.Inject import javax.inject.Singleton @@ -49,9 +52,20 @@ class BookRepositoryImpl @Inject constructor( emit(Resource.Loading(true)) val books = database.searchBooks(query) + val history = database.getHistory() + emit( Resource.Success( - data = books.map { bookMapper.toBook(it) } + data = books.map { entity -> + val book = bookMapper.toBook(entity) + val lastHistory = history + .filter { it.bookId == book.id } + .maxByOrNull { it.time } + + book.copy( + lastOpened = lastHistory?.time + ) + } ) ) } @@ -59,7 +73,21 @@ class BookRepositoryImpl @Inject constructor( override suspend fun fastGetBooks(query: String): List { val books = database.searchBooks(query) - return books.map { bookMapper.toBook(it) } + val history = database.getHistory() + + return books.map { entity -> + val book = bookMapper.toBook(entity) + val lastHistory = + history.filter { it.bookId == book.id }.maxByOrNull { it.time } + + book.copy( + lastOpened = lastHistory?.time + ) + } + } + + override suspend fun getBooksById(ids: List): List { + return database.findBooksById(ids).map { bookMapper.toBook(it) } } override suspend fun findBook( @@ -154,7 +182,10 @@ class BookRepositoryImpl @Inject constructor( filteredFiles = if (query.trim().isNotEmpty()) { filteredFiles.sortedByDescending { - it.name.lowercase().startsWith(query.trim().lowercase()) + calculateFamiliarity( + query, + it.name.lowercase().trim() + ) }.toMutableList() } else { filteredFiles.sortedByDescending { @@ -182,61 +213,113 @@ class BookRepositoryImpl @Inject constructor( } else if (file.name.endsWith(".epub")) { epubTextParser.parse(file) } else { + emit(Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))) return@flow } - emit(Resource.Success(text)) - } - } - - override suspend fun getBooksFromFiles(files: List): Flow>> { - return flow { - emit(Resource.Loading(true)) - - val books = mutableListOf() - for (file in files) { - - val parsedBook = if (file.name.endsWith(".txt")) { - txtFileParser.parse(file) - } else if (file.name.endsWith(".pdf")) { - pdfFileParser.parse(file) - } else if (file.name.endsWith(".epub")) { - epubFileParser.parse(file) - } else { - books.add(NullableBook.Null(file.name)) - continue - } - - val parsedText = if (file.name.endsWith(".txt")) { - txtTextParser.parse(file) - } else if (file.name.endsWith(".pdf")) { - pdfTextParser.parse(file) - } else if (file.name.endsWith(".epub")) { - epubTextParser.parse(file) - } else { - books.add(NullableBook.Null(file.name)) - continue - } - - if (parsedBook == null || parsedText.isEmpty()) { - books.add(NullableBook.Null(file.name)) - continue - } - - books.add( - NullableBook.NotNull( - Pair( - parsedBook.copy(text = parsedText), - true - ) + if (text is Resource.Success && text.data == null) { + emit( + Resource.Error( + text.message + ?: UIText.StringResource(R.string.error_file_empty) ) ) } - emit(Resource.Success(books)) + when (text) { + is Resource.Success -> emit(Resource.Success(text.data)) + is Resource.Error -> emit( + Resource.Error( + text.message + ?: UIText.StringResource(R.string.error_something_went_wrong) + ) + ) + + is Resource.Loading -> Unit + } + } } + override suspend fun getBooksFromFiles(files: List): List { + val books = mutableListOf() + + for (file in files) { + val parsedBook = if (file.name.endsWith(".txt")) { + txtFileParser.parse(file) + } else if (file.name.endsWith(".pdf")) { + pdfFileParser.parse(file) + } else if (file.name.endsWith(".epub")) { + epubFileParser.parse(file) + } else { + books.add( + NullableBook.Null( + file.name, + UIText.StringResource(R.string.error_wrong_file_format) + ) + ) + continue + } + + val parsedText = if (file.name.endsWith(".txt")) { + txtTextParser.parse(file) + } else if (file.name.endsWith(".pdf")) { + pdfTextParser.parse(file) + } else if (file.name.endsWith(".epub")) { + epubTextParser.parse(file) + } else { + books.add( + NullableBook.Null( + file.name, + UIText.StringResource(R.string.error_wrong_file_format) + ) + ) + continue + } + + if (parsedBook == null) { + books.add( + NullableBook.Null( + file.name, + UIText.StringResource(R.string.error_something_went_wrong) + ) + ) + continue + } + + if (parsedText is Resource.Error) { + books.add( + NullableBook.Null( + file.name, + parsedText.message + ) + ) + continue + } + + if (parsedText.data == null) { + books.add( + NullableBook.Null( + file.name, + UIText.StringResource(R.string.error_file_empty) + ) + ) + continue + } + + books.add( + NullableBook.NotNull( + Pair( + parsedBook.copy(text = parsedText.data), + true + ) + ) + ) + } + + return books + } + override suspend fun insertHistory(history: List) { database.insertHistory(history.map { historyMapper.toHistoryEntity(it) }) } @@ -246,11 +329,13 @@ class BookRepositoryImpl @Inject constructor( emit(Resource.Loading(true)) val history = database.getHistory() + emit( Resource.Success( data = history.map { - val book = database.findBookById(it.bookId) - historyMapper.toHistory(it, bookMapper.toBook(book)) + historyMapper.toHistory( + it + ) } ) ) diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt index 985463a3..0d72a8b0 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt @@ -1,7 +1,7 @@ package ua.acclorite.book_story.domain.model -import android.graphics.Bitmap import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.ImageBitmap import ua.acclorite.book_story.util.UIText import java.io.File @@ -17,5 +17,5 @@ data class Book( val filePath: String, val lastOpened: Long?, val category: Category, - val coverImage: Bitmap? + val coverImage: ImageBitmap? ) diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/Category.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/Category.kt index dfbb5321..acab1534 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/Category.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/model/Category.kt @@ -1,5 +1,8 @@ package ua.acclorite.book_story.domain.model +import androidx.compose.runtime.Immutable + +@Immutable enum class Category { READING, ALREADY_READ, PLANNING, DROPPED } diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/History.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/History.kt index 1e9d8ada..4bf92477 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/History.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/model/History.kt @@ -5,6 +5,6 @@ import androidx.compose.runtime.Immutable @Immutable data class History( val id: Int?, - val book: Book, + val bookId: Int, val time: Long ) \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/NavigationBarItem.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/NavigationItem.kt similarity index 88% rename from app/src/main/java/ua/acclorite/book_story/domain/model/NavigationBarItem.kt rename to app/src/main/java/ua/acclorite/book_story/domain/model/NavigationItem.kt index ff897133..3e62e80c 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/NavigationBarItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/model/NavigationItem.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.painter.Painter @Immutable -data class NavigationBarItem( +data class NavigationItem( val title: String, val selectedIcon: Painter, val unselectedIcon: Painter diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt index 014b0940..7af9a63c 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt @@ -1,6 +1,17 @@ package ua.acclorite.book_story.domain.model -sealed class NullableBook(val book: Pair?, val fileName: String?) { - class NotNull(book: Pair) : NullableBook(book, null) - class Null(fileName: String) : NullableBook(null, fileName) -} \ No newline at end of file +import androidx.compose.runtime.Immutable +import ua.acclorite.book_story.util.UIText + +@Immutable +sealed class NullableBook( + val book: Pair?, + val fileName: String?, + val message: UIText? +) { + class NotNull(book: Pair) : NullableBook(book, null, null) + class Null( + fileName: String, + message: UIText? + ) : NullableBook(null, fileName, message) +} diff --git a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt index b2bf1844..edf27f7d 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt @@ -20,6 +20,10 @@ interface BookRepository { query: String ): List + suspend fun getBooksById( + ids: List + ): List + suspend fun findBook( id: Int ): BookEntity @@ -50,7 +54,7 @@ interface BookRepository { suspend fun getBookTextFromFile(file: File): Flow>> - suspend fun getBooksFromFiles(files: List): Flow>> + suspend fun getBooksFromFiles(files: List): List suspend fun insertHistory(history: List) diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBookFromFile.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBookFromFile.kt new file mode 100644 index 00000000..b98c325e --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBookFromFile.kt @@ -0,0 +1,15 @@ +package ua.acclorite.book_story.domain.use_case + +import ua.acclorite.book_story.domain.model.NullableBook +import ua.acclorite.book_story.domain.repository.BookRepository +import java.io.File +import javax.inject.Inject + +class GetBookFromFile @Inject constructor( + private val repository: BookRepository +) { + + suspend fun execute(file: File): NullableBook { + return repository.getBooksFromFiles(listOf(file)).first() + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBooksById.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBooksById.kt new file mode 100644 index 00000000..f854cf2d --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBooksById.kt @@ -0,0 +1,12 @@ +package ua.acclorite.book_story.domain.use_case + +import ua.acclorite.book_story.domain.model.Book +import ua.acclorite.book_story.domain.repository.BookRepository +import javax.inject.Inject + +class GetBooksById @Inject constructor(private val repository: BookRepository) { + + suspend fun execute(ids: List): List { + return repository.getBooksById(ids) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBooksFromFiles.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBooksFromFiles.kt index b8d0374d..48bf6413 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBooksFromFiles.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBooksFromFiles.kt @@ -1,9 +1,7 @@ package ua.acclorite.book_story.domain.use_case -import kotlinx.coroutines.flow.Flow import ua.acclorite.book_story.domain.model.NullableBook import ua.acclorite.book_story.domain.repository.BookRepository -import ua.acclorite.book_story.util.Resource import java.io.File import javax.inject.Inject @@ -11,7 +9,7 @@ class GetBooksFromFiles @Inject constructor( private val repository: BookRepository ) { - suspend fun execute(files: List): Flow>> { + suspend fun execute(files: List): List { return repository.getBooksFromFiles(files) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/AnimatedTopAppBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/AnimatedTopAppBar.kt index 275ee0a6..f68d490c 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/AnimatedTopAppBar.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/AnimatedTopAppBar.kt @@ -26,6 +26,14 @@ import androidx.compose.ui.unit.dp import ua.acclorite.book_story.ui.DefaultTransition import ua.acclorite.book_story.ui.elevation +/** + * Animated top app bar. Has up to 3 different top bars inside it. Follow [TopAppBar] for more info. + * + * @param isTopBarScrolled Whether isScrolled state should be forced or not. + * @param content1Visibility Whether first top app bar should be shown. + * @param content2Visibility Whether second top app bar should be shown. + * @param content3Visibility Whether third top app bar should be shown. + */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun AnimatedTopAppBar( @@ -50,6 +58,7 @@ fun AnimatedTopAppBar( content3Title: @Composable () -> Unit = {}, content3Actions: @Composable RowScope.() -> Unit = {} ) { + //todo fix lags. Box(modifier = Modifier.fillMaxWidth()) { if ((scrollBehavior != null || isTopBarScrolled != null) && scrolledContainerColor != null) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/CategoryTitle.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/CategoryTitle.kt index c553bf4c..be7079a7 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/CategoryTitle.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/CategoryTitle.kt @@ -14,6 +14,9 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +/** + * Category Title. Used to name a category. + */ @Composable fun CategoryTitle( modifier: Modifier = Modifier, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomCheckbox.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomCheckbox.kt index addfddca..b63df96b 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomCheckbox.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomCheckbox.kt @@ -16,6 +16,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import ua.acclorite.book_story.ui.elevation +/** + * Custom Checkbox. Has a Circle shape. + */ @Composable fun CustomCheckbox(selected: Boolean) { Icon( diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomDropDownMenuItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomDropDownMenuItem.kt index f4353a5b..b6dc35b1 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomDropDownMenuItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomDropDownMenuItem.kt @@ -11,6 +11,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.dp +/** + * Custom Dropdown Menu Item. Used in Dropdown. + */ @Composable fun CustomDropDownMenuItem( leadingIcon: ImageVector, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSelectionContainer.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSelectionContainer.kt new file mode 100644 index 00000000..e67b4435 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSelectionContainer.kt @@ -0,0 +1,236 @@ +package ua.acclorite.book_story.presentation.components + +import android.content.ClipboardManager +import android.content.Context +import android.view.ActionMode +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalTextToolbar +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.TextToolbar +import androidx.compose.ui.platform.TextToolbarStatus +import ua.acclorite.book_story.R + + +private const val MENU_ITEM_COPY = 0 +private const val MENU_ITEM_TRANSLATE = 1 +private const val MENU_ITEM_DICTIONARY = 2 + +/** + * Custom Text ActionMode callback. Used in pair with [CustomSelectionToolbar]. Follow [TextToolbar] for more info. + */ +private class CustomTextActionModeCallback( + private val context: Context, + var rect: Rect = Rect.Zero, + var onCopyRequested: (() -> Unit)? = null, + var onTranslateRequested: (() -> Unit)? = null, + var onDictionaryRequested: (() -> Unit)? = null +) : ActionMode.Callback { + override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { + requireNotNull(menu) + requireNotNull(mode) + + onCopyRequested?.let { + menu.add(0, MENU_ITEM_COPY, 0, context.getString(R.string.copy)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) + } + + onTranslateRequested?.let { + menu.add(0, MENU_ITEM_TRANSLATE, 1, context.getString(R.string.translate)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) + } + + onDictionaryRequested?.let { + menu.add(0, MENU_ITEM_DICTIONARY, 2, context.getString(R.string.dictionary)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) + } + + return true + } + + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { + return false + } + + override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { + when (item!!.itemId) { + MENU_ITEM_COPY -> onCopyRequested?.invoke() + MENU_ITEM_TRANSLATE -> onTranslateRequested?.invoke() + MENU_ITEM_DICTIONARY -> onDictionaryRequested?.invoke() + else -> return false + } + mode?.finish() + return true + } + + override fun onDestroyActionMode(mode: ActionMode?) {} +} + +/** + * Floating [CustomTextActionModeCallback]. + */ +private class FloatingTextActionModeCallback( + val callback: CustomTextActionModeCallback +) : ActionMode.Callback2() { + override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { + return callback.onActionItemClicked(mode, item) + } + + override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { + return callback.onCreateActionMode(mode, menu) + } + + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { + return callback.onPrepareActionMode(mode, menu) + } + + override fun onDestroyActionMode(mode: ActionMode?) { + callback.onDestroyActionMode(mode) + } + + override fun onGetContentRect(mode: ActionMode?, view: View?, outRect: android.graphics.Rect?) { + val rect = callback.rect + outRect?.set( + rect.left.toInt(), + rect.top.toInt(), + rect.right.toInt(), + rect.bottom.toInt() + ) + } +} + +/** + * Custom Selection Toolbar. Used in pair with [CustomSelectionContainer] to display custom toolbar. + */ +private class CustomSelectionToolbar( + private val view: View, + context: Context, + private val onCopyRequest: (() -> Unit)?, + private val onTranslateRequest: ((String) -> Unit)?, + private val onDictionaryRequest: ((String) -> Unit)? +) : TextToolbar { + private var actionMode: ActionMode? = null + private val callback = CustomTextActionModeCallback(context = context) + + val clipboardManager = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + + override var status: TextToolbarStatus by mutableStateOf(TextToolbarStatus.Hidden) + private set + + override fun showMenu( + rect: Rect, + onCopyRequested: (() -> Unit)?, + onPasteRequested: (() -> Unit)?, + onCutRequested: (() -> Unit)?, + onSelectAllRequested: (() -> Unit)? + ) { + callback.rect = rect + callback.onCopyRequested = { + onCopyRequested?.invoke() + onCopyRequest?.invoke() + } + callback.onTranslateRequested = { + val previousClipboard = clipboardManager.primaryClip + onCopyRequested?.invoke() + val currentClipboard = clipboardManager.text + + onTranslateRequest?.invoke(currentClipboard.toString()) + + if (previousClipboard != null) { + clipboardManager.setPrimaryClip( + previousClipboard + ) + } + } + callback.onDictionaryRequested = { + val previousClipboard = clipboardManager.primaryClip + onCopyRequested?.invoke() + val currentClipboard = clipboardManager.text + + onDictionaryRequest?.invoke(currentClipboard.toString()) + + if (previousClipboard != null) { + clipboardManager.setPrimaryClip( + previousClipboard + ) + } + } + + if (actionMode == null) { + status = TextToolbarStatus.Shown + actionMode = view.startActionMode( + FloatingTextActionModeCallback(callback), + ActionMode.TYPE_FLOATING + ) + } else { + actionMode?.invalidate() + } + } + + override fun hide() { + status = TextToolbarStatus.Hidden + actionMode?.finish() + actionMode = null + } +} + +/** + * Custom selection container. + * + * @param onCopyRequested Callback for when the copy option is clicked. + * @param onTranslateRequested Callback for when the translate option is clicked. + * @param onDictionaryRequested Callback for when the dictionary option is clicked. + * @param content Selection container content. + */ +@Composable +fun CustomSelectionContainer( + onCopyRequested: (() -> Unit), + onTranslateRequested: ((String) -> Unit), + onDictionaryRequested: ((String) -> Unit), + content: @Composable (Boolean) -> Unit +) { + val view = LocalView.current + val context = LocalContext.current + + val customSelectionToolbar = remember { + CustomSelectionToolbar( + view = view, + context = context, + + onCopyRequest = { + onCopyRequested() + }, + onTranslateRequest = { + onTranslateRequested(it) + }, + onDictionaryRequest = { + onDictionaryRequested(it) + } + ) + } + val isToolbarHidden = remember(customSelectionToolbar.status) { + derivedStateOf { + customSelectionToolbar.status == TextToolbarStatus.Hidden + } + } + + CompositionLocalProvider( + LocalTextToolbar provides customSelectionToolbar + ) { + SelectionContainer { + content(isToolbarHidden.value) + } + } +} diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSnackBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSnackBar.kt deleted file mode 100644 index b23809c6..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSnackBar.kt +++ /dev/null @@ -1,49 +0,0 @@ -package ua.acclorite.book_story.presentation.components - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.DismissValue -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.SwipeToDismiss -import androidx.compose.material.rememberDismissState -import androidx.compose.material3.Snackbar -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun CustomSnackbar(snackbarState: SnackbarHostState) { - val dismissState = rememberDismissState { value -> - if (value != DismissValue.Default) { - snackbarState.currentSnackbarData?.dismiss() - true - } else false - } - - LaunchedEffect(dismissState.currentValue) { - if (dismissState.currentValue != DismissValue.Default) { - dismissState.reset() - } - } - - SwipeToDismiss( - modifier = Modifier - .padding(vertical = 4.dp) - .fillMaxWidth(), - state = dismissState, - background = {} - ) { - SnackbarHost( - hostState = snackbarState, - snackbar = { data -> - Snackbar( - data - ) - } - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSnackbar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSnackbar.kt new file mode 100644 index 00000000..46e76aac --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSnackbar.kt @@ -0,0 +1,100 @@ +package ua.acclorite.book_story.presentation.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsDraggedAsState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Snackbar +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +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.platform.LocalDensity +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import kotlin.math.roundToInt + +/** + * Custom Snackbar with vertical Swipe-To-Dismiss and animation. + * It is recommended to put it in Scaffold bottomBar(this way padding is the same at all sides) + * + * @param snackbarState Regular [SnackbarHostState], use ..remember { SnackbarHostState() } + */ +@Composable +fun CustomSnackbar(modifier: Modifier = Modifier, snackbarState: SnackbarHostState) { + var showSnackbar by remember { mutableStateOf(false) } + var snackbarData by remember { mutableStateOf(snackbarState.currentSnackbarData) } + var offset by remember { mutableFloatStateOf(0f) } + + val density = LocalDensity.current + val mutableInteractionSource = remember { MutableInteractionSource() } + val isDragged by mutableInteractionSource.collectIsDraggedAsState() + + LaunchedEffect(offset, isDragged) { + val offsetDp = with(density) { offset.toDp() } + if (offsetDp > 35.dp && !isDragged) { + snackbarState.currentSnackbarData?.dismiss() + return@LaunchedEffect + } + + if (offsetDp <= 35.dp && !isDragged) { + offset = 0f + } + } + LaunchedEffect(snackbarState.currentSnackbarData) { + val data = snackbarState.currentSnackbarData + + if (data == null) { + showSnackbar = false + return@LaunchedEffect + } + + offset = 0f + snackbarData = data + showSnackbar = true + } + + AnimatedVisibility( + visible = showSnackbar, + enter = slideInVertically(tween(250)) { it } + fadeIn(tween(250)), + exit = slideOutVertically(tween(100)) { it } + fadeOut(tween(100)) + ) { + Box( + modifier = modifier + .offset { IntOffset(0, offset.roundToInt()) } + .padding(vertical = 4.dp) + .fillMaxWidth() + .draggable( + interactionSource = mutableInteractionSource, + orientation = Orientation.Vertical, + state = rememberDraggableState { + offset = (offset + it).coerceAtLeast(0f) + } + ) + ) { + Snackbar( + modifier = Modifier.align(Alignment.Center), + snackbarData = snackbarData!!, + shape = MaterialTheme.shapes.medium + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/GoBackButton.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/GoBackButton.kt new file mode 100644 index 00000000..70afc399 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/GoBackButton.kt @@ -0,0 +1,38 @@ +package ua.acclorite.book_story.presentation.components + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +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.res.stringResource +import ua.acclorite.book_story.R +import ua.acclorite.book_story.presentation.data.Navigator + +/** + * Go back arrow button. Prevents double or triple clicking on it while going back action is performed. + */ +@Composable +fun GoBackButton(navigator: Navigator, customOnClick: () -> Unit = {}) { + var isClicked by remember { mutableStateOf(false) } + + IconButton( + enabled = !isClicked, + onClick = { + isClicked = true + navigator.navigateBack() + customOnClick() + } + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(id = R.string.go_back_content_desc), + tint = MaterialTheme.colorScheme.onSurface + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/MoreDropDown.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/MoreDropDown.kt index c5cd1e4b..f71eb974 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/MoreDropDown.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/MoreDropDown.kt @@ -21,9 +21,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.Screen +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.data.Screen +/** + * More drop down. Navigates to Settings, About and Help screens. + */ @Composable fun MoreDropDown(navigator: Navigator) { var showDropDown by remember { mutableStateOf(false) } @@ -58,7 +61,7 @@ fun MoreDropDown(navigator: Navigator) { ) }, onClick = { - navigator.navigate(Screen.SETTINGS) + navigator.navigate(Screen.SETTINGS, false) showDropDown = false }, contentPadding = PaddingValues(start = startPadding, end = endPadding) @@ -72,7 +75,7 @@ fun MoreDropDown(navigator: Navigator) { ) }, onClick = { - navigator.navigate(Screen.HELP) + navigator.navigate(Screen.HELP, false) showDropDown = false }, contentPadding = PaddingValues(start = startPadding, end = endPadding) @@ -86,7 +89,7 @@ fun MoreDropDown(navigator: Navigator) { ) }, onClick = { - navigator.navigate(Screen.ABOUT) + navigator.navigate(Screen.ABOUT, false) showDropDown = false }, contentPadding = PaddingValues(start = startPadding, end = endPadding) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/BottomNavigationBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/bottom_navigation_bar/BottomNavigationBar.kt similarity index 80% rename from app/src/main/java/ua/acclorite/book_story/presentation/components/BottomNavigationBar.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/components/bottom_navigation_bar/BottomNavigationBar.kt index b50bc246..e2cb079d 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/BottomNavigationBar.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/bottom_navigation_bar/BottomNavigationBar.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.components +package ua.acclorite.book_story.presentation.components.bottom_navigation_bar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBar @@ -11,9 +11,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.model.NavigationBarItem -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.Screen +import ua.acclorite.book_story.domain.model.NavigationItem +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.ui.elevation /** @@ -37,34 +37,34 @@ fun BottomNavigationBar( containerColor = MaterialTheme.elevation() ) { BottomNavigationBarItem( - item = NavigationBarItem( + item = NavigationItem( stringResource(id = R.string.library_screen), selectedIcon = painterResource(id = R.drawable.library_screen_filled), unselectedIcon = painterResource(id = R.drawable.library_screen_outlined) ), isSelected = currentScreen == Screen.LIBRARY ) { - navigator.navigate(Screen.LIBRARY) + navigator.navigate(Screen.LIBRARY, false) } BottomNavigationBarItem( - item = NavigationBarItem( + item = NavigationItem( stringResource(id = R.string.history_screen), selectedIcon = painterResource(id = R.drawable.history_screen_filled), unselectedIcon = painterResource(id = R.drawable.history_screen_outlined) ), isSelected = currentScreen == Screen.HISTORY ) { - navigator.navigate(Screen.HISTORY) + navigator.navigate(Screen.HISTORY, false) } BottomNavigationBarItem( - item = NavigationBarItem( + item = NavigationItem( stringResource(id = R.string.browse_screen), selectedIcon = painterResource(id = R.drawable.browse_screen_filled), unselectedIcon = painterResource(id = R.drawable.browse_screen_outlined) ), isSelected = currentScreen == Screen.BROWSE ) { - navigator.navigate(Screen.BROWSE) + navigator.navigate(Screen.BROWSE, false) } } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/BottomNavigationItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/bottom_navigation_bar/BottomNavigationItem.kt similarity index 91% rename from app/src/main/java/ua/acclorite/book_story/presentation/components/BottomNavigationItem.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/components/bottom_navigation_bar/BottomNavigationItem.kt index 94c8edac..eda6f49f 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/BottomNavigationItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/bottom_navigation_bar/BottomNavigationItem.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.components +package ua.acclorite.book_story.presentation.components.bottom_navigation_bar import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.size @@ -11,7 +11,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import ua.acclorite.book_story.domain.model.NavigationBarItem +import ua.acclorite.book_story.domain.model.NavigationItem /** * Bottom Navigation Bar Item, uses default [NavigationBarItem]. @@ -19,7 +19,7 @@ import ua.acclorite.book_story.domain.model.NavigationBarItem @Composable fun RowScope.BottomNavigationBarItem( modifier: Modifier = Modifier, - item: NavigationBarItem, + item: NavigationItem, isSelected: Boolean, onClick: () -> Unit ) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomDialogWithContent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithContent.kt similarity index 87% rename from app/src/main/java/ua/acclorite/book_story/presentation/components/CustomDialogWithContent.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithContent.kt index a7751c64..0fc92a81 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomDialogWithContent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithContent.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.components +package ua.acclorite.book_story.presentation.components.custom_dialog import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -35,6 +35,23 @@ import ua.acclorite.book_story.R import ua.acclorite.book_story.ui.ElevationDefaults import ua.acclorite.book_story.ui.elevation +/** + * Custom Dialog with custom content. + * + * @param modifier Modifier to be applied. + * @param properties Dialog properties. + * @param drawableIcon Drawable icon to show(1/2). + * @param imageVectorIcon Image vector icon to show(2/2). + * @param backgroundTransparency The transparency of the background behind the dialog. + * @param title The title of the dialog. + * @param description The description of the dialog. + * @param actionText The action text of the dialog. + * @param isActionEnabled Whether action is enabled or not, + * @param onDismiss Code to execute when the dialog is dismissed. + * @param onAction Code to execute when action is clicked. + * @param withDivider Should divider be shown or not. + * @param customContent Custom content(not necessary, dialog also can be use just as alert dialog). + */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun CustomDialogWithContent( @@ -54,7 +71,7 @@ fun CustomDialogWithContent( ) { AlertDialog( onDismissRequest = { onDismiss() }, - properties = properties, + properties = properties ) { (LocalView.current.parent as DialogWindowProvider).window.setDimAmount( backgroundTransparency @@ -118,8 +135,8 @@ fun CustomDialogWithContent( if (withDivider) { Spacer(modifier = Modifier.height(12.dp)) Divider( - color = MaterialTheme.colorScheme.outlineVariant, - modifier = Modifier.padding(horizontal = 24.dp) + modifier = Modifier.padding(horizontal = 24.dp), + color = MaterialTheme.colorScheme.outlineVariant ) Spacer(modifier = Modifier.height(12.dp)) } else { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomDialogWithLazyColumn.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithLazyColumn.kt similarity index 88% rename from app/src/main/java/ua/acclorite/book_story/presentation/components/CustomDialogWithLazyColumn.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithLazyColumn.kt index 51cf8bb1..0e7ea033 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomDialogWithLazyColumn.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithLazyColumn.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.components +package ua.acclorite.book_story.presentation.components.custom_dialog import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -36,6 +36,24 @@ import ua.acclorite.book_story.R import ua.acclorite.book_story.ui.ElevationDefaults import ua.acclorite.book_story.ui.elevation + +/** + * Custom Dialog with lazy column. + * + * @param modifier Modifier to be applied. + * @param properties Dialog properties. + * @param drawableIcon Drawable icon to show(1/2). + * @param imageVectorIcon Image vector icon to show(2/2). + * @param backgroundTransparency The transparency of the background behind the dialog. + * @param title The title of the dialog. + * @param description The description of the dialog. + * @param actionText The action text of the dialog. + * @param isActionEnabled Whether action is enabled or not, + * @param onDismiss Code to execute when the dialog is dismissed. + * @param onAction Code to execute when action is clicked. + * @param withDivider Should divider be shown or not. + * @param items The items of this dialog, use items(..) { .. } or item { .. }. + */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun CustomDialogWithLazyColumn( @@ -55,7 +73,7 @@ fun CustomDialogWithLazyColumn( ) { AlertDialog( onDismissRequest = { onDismiss() }, - properties = properties, + properties = properties ) { (LocalView.current.parent as DialogWindowProvider).window.setDimAmount( backgroundTransparency @@ -119,8 +137,8 @@ fun CustomDialogWithLazyColumn( if (withDivider) { Spacer(modifier = Modifier.height(12.dp)) Divider( - color = MaterialTheme.colorScheme.outlineVariant, - modifier = Modifier.padding(horizontal = 24.dp) + modifier = Modifier.padding(horizontal = 24.dp), + color = MaterialTheme.colorScheme.outlineVariant ) Spacer(modifier = Modifier.height(12.dp)) } else { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/SelectableDialogItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/SelectableDialogItem.kt similarity index 91% rename from app/src/main/java/ua/acclorite/book_story/presentation/components/SelectableDialogItem.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/SelectableDialogItem.kt index 7c638013..7e476e36 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/SelectableDialogItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/SelectableDialogItem.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.components +package ua.acclorite.book_story.presentation.components.custom_dialog import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Row @@ -17,6 +17,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +/** + * Selectable dialog item. Perfectly combines with [CustomDialogWithLazyColumn]. + */ @Composable fun SelectableDialogItem(selected: Boolean, title: String, onClick: () -> Unit) { Row( diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_navigation_rail/CustomNavigationRail.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_navigation_rail/CustomNavigationRail.kt new file mode 100644 index 00000000..939d68ea --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_navigation_rail/CustomNavigationRail.kt @@ -0,0 +1,90 @@ +package ua.acclorite.book_story.presentation.components.custom_navigation_rail + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationRail +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import ua.acclorite.book_story.R +import ua.acclorite.book_story.domain.model.NavigationItem +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.data.Screen +import ua.acclorite.book_story.ui.elevation + +/** + * Custom Navigation Rail. It is used to be shown on Tablets. + */ +@Composable +fun CustomNavigationRail( + navigator: Navigator +) { + var currentScreen: Screen? by remember { mutableStateOf(null) } + + LaunchedEffect(Unit) { + navigator.getCurrentScreen().collect { + if (it == Screen.LIBRARY || it == Screen.HISTORY || it == Screen.BROWSE) { + currentScreen = it + } + } + } + + NavigationRail( + modifier = Modifier + .width(80.dp) + .background(MaterialTheme.elevation()) + .padding(horizontal = 12.dp), + containerColor = MaterialTheme.elevation() + ) { + Column( + Modifier + .fillMaxHeight(), + verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + CustomNavigationRailItem( + item = NavigationItem( + stringResource(id = R.string.library_screen), + selectedIcon = painterResource(id = R.drawable.library_screen_filled), + unselectedIcon = painterResource(id = R.drawable.library_screen_outlined) + ), + isSelected = currentScreen == Screen.LIBRARY + ) { + navigator.navigate(Screen.LIBRARY, false) + } + CustomNavigationRailItem( + item = NavigationItem( + stringResource(id = R.string.history_screen), + selectedIcon = painterResource(id = R.drawable.history_screen_filled), + unselectedIcon = painterResource(id = R.drawable.history_screen_outlined) + ), + isSelected = currentScreen == Screen.HISTORY + ) { + navigator.navigate(Screen.HISTORY, false) + } + CustomNavigationRailItem( + item = NavigationItem( + stringResource(id = R.string.browse_screen), + selectedIcon = painterResource(id = R.drawable.browse_screen_filled), + unselectedIcon = painterResource(id = R.drawable.browse_screen_outlined) + ), + isSelected = currentScreen == Screen.BROWSE + ) { + navigator.navigate(Screen.BROWSE, false) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_navigation_rail/CustomNavigationRailItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_navigation_rail/CustomNavigationRailItem.kt new file mode 100644 index 00000000..80ed8e28 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_navigation_rail/CustomNavigationRailItem.kt @@ -0,0 +1,54 @@ +package ua.acclorite.book_story.presentation.components.custom_navigation_rail + +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationRailItem +import androidx.compose.material3.NavigationRailItemDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import ua.acclorite.book_story.domain.model.NavigationItem + +/** + * Custom Navigation Rail item. + */ +@Composable +fun CustomNavigationRailItem( + modifier: Modifier = Modifier, + item: NavigationItem, + isSelected: Boolean, + onClick: () -> Unit +) { + NavigationRailItem( + label = { + Text( + text = item.title, + style = MaterialTheme.typography.labelMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + selected = isSelected, + enabled = !isSelected, + onClick = { onClick() }, + icon = { + Icon( + painter = + if (isSelected) item.selectedIcon + else item.unselectedIcon, + contentDescription = item.title, + modifier = Modifier.size(24.dp) + ) + }, + modifier = modifier, + colors = NavigationRailItemDefaults.colors( + unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant, + unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant, + disabledIconColor = MaterialTheme.colorScheme.onSurface, + disabledTextColor = MaterialTheme.colorScheme.onSurface + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/IsEmpty.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/is_messages/IsEmpty.kt similarity index 87% rename from app/src/main/java/ua/acclorite/book_story/presentation/components/IsEmpty.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/components/is_messages/IsEmpty.kt index 0a4d67a5..311afe73 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/IsEmpty.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/is_messages/IsEmpty.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.components +package ua.acclorite.book_story.presentation.components.is_messages import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -20,6 +20,16 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp + +/** + * Is Empty message. It usually placed when the list is empty. + * + * @param modifier Modifier to be applied. + * @param message The message. + * @param icon Icon to be displayed. + * @param actionTitle Optional action. + * @param action On click action. + */ @Composable fun IsEmpty( modifier: Modifier = Modifier, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/IsError.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/is_messages/IsError.kt similarity index 85% rename from app/src/main/java/ua/acclorite/book_story/presentation/components/IsError.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/components/is_messages/IsError.kt index 61bf90b1..25e5e20b 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/IsError.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/is_messages/IsError.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.components +package ua.acclorite.book_story.presentation.components.is_messages import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -20,6 +20,15 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +/** + * Is Empty message. It usually placed when something went wrong and you need to display large error message. + * + * @param modifier Modifier to be applied. + * @param errorMessage The error message. + * @param icon Icon to be displayed. + * @param actionTitle Optional action. + * @param action On click action. + */ @Composable fun IsError( modifier: Modifier = Modifier, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/ArithmeticOperations.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/ArithmeticOperations.kt index 71b426bd..e80b1fc2 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/data/ArithmeticOperations.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/ArithmeticOperations.kt @@ -1,5 +1,9 @@ package ua.acclorite.book_story.presentation.data +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Path +import kotlin.math.abs + fun String.removeTrailingZero(): String { if (!this.contains('.')) return this @@ -8,4 +12,19 @@ fun String.removeTrailingZero(): String { .dropLastWhile { it == '.' } } -fun Double.removeDigits(digits: Int) = "%.${digits}f".format(this).replace(",", ".") \ No newline at end of file +fun Double.removeDigits(digits: Int) = "%.${digits}f".format(this).replace(",", ".") + +fun calculateFamiliarity(string: String, target: String): Int { + val targetCounts = target.lowercase().trim().groupingBy { it }.eachCount() + val familiarity = string.lowercase().trim().sumOf { targetCounts.getOrDefault(it, 0) } + return familiarity +} + +fun Path.standardQuadFromTo(from: Offset, to: Offset) { + quadraticTo( + from.x, + from.y, + abs(from.x + to.x) / 2f, + abs(from.y + to.y) / 2f + ) +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt index 73b2fffe..08020779 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.launch import ua.acclorite.book_story.domain.use_case.ChangeLanguage import ua.acclorite.book_story.domain.use_case.GetDatastore import ua.acclorite.book_story.domain.use_case.SetDatastore +import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel import ua.acclorite.book_story.ui.DarkTheme import ua.acclorite.book_story.ui.Theme import ua.acclorite.book_story.ui.toDarkTheme @@ -264,19 +265,47 @@ class MainViewModel @Inject constructor( } } - fun init(activity: ComponentActivity) { + fun init( + activity: ComponentActivity, + libraryViewModel: LibraryViewModel, + ) { + val isViewModelsReady = combine( + libraryViewModel.isReady, + ) { values -> + values.all { it } + } + val isDataReady = combine( - language, theme, darkTheme, showStartScreen, backgroundColor, - fontColor, fontFamily, isItalic, fontSize, lineHeight, - paragraphHeight, paragraphIndentation + language, + theme, + darkTheme, + showStartScreen, + backgroundColor, + fontColor, + fontFamily, + isItalic, + fontSize, + lineHeight, + paragraphHeight, + paragraphIndentation ) { values -> values.all { it != null } } + val isReady = combine( + isViewModelsReady, + isDataReady + ) { values -> + values.all { it } + } + viewModelScope.launch { - isDataReady.collect { bool -> + isReady.collect { bool -> if (bool) { - _isReady.value = true + _isReady.update { + true + } + return@collect } } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/Navigation.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/Navigation.kt similarity index 81% rename from app/src/main/java/ua/acclorite/book_story/presentation/Navigation.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/data/Navigation.kt index ac384499..c9cf246e 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/Navigation.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/Navigation.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation +package ua.acclorite.book_story.presentation.data import android.annotation.SuppressLint import androidx.activity.ComponentActivity @@ -33,6 +33,7 @@ import ua.acclorite.book_story.ui.Transitions private const val CURRENT_SCREEN = "current_screen" private const val BACKSTACK = "back_stack" +private const val USE_BACK_ANIM = "use_back_animation" /** * All screens are listed here, later each screen will be passed as a param for [Navigator.composable] function. @@ -48,9 +49,11 @@ enum class Screen { SETTINGS, GENERAL_SETTINGS, APPEARANCE_SETTINGS, + READER_SETTINGS, ABOUT, - HELP + HELP, + START } data class Argument( @@ -72,6 +75,7 @@ class Navigator @AssistedInject constructor( ) : ViewModel() { private val currentScreen = savedStateHandle.getStateFlow(CURRENT_SCREEN, startScreen) + private val useBackAnimation = savedStateHandle.getStateFlow(USE_BACK_ANIM, false) private val backStack = savedStateHandle.getStateFlow(BACKSTACK, mutableListOf()) private val arguments = mutableStateListOf() @@ -104,7 +108,7 @@ class Navigator @AssistedInject constructor( arguments.removeIf { it.key == key } } - fun navigate(screen: Screen, vararg args: Argument) = + fun navigate(screen: Screen, useBackAnimation: Boolean, vararg args: Argument) = viewModelScope.launch(Dispatchers.Default) { backStack.value.add(currentScreen.value) @@ -112,10 +116,11 @@ class Navigator @AssistedInject constructor( putArgument(it) } + savedStateHandle[USE_BACK_ANIM] = useBackAnimation savedStateHandle[CURRENT_SCREEN] = screen } - fun navigateWithoutBackStack(screen: Screen, vararg args: Argument) = + fun navigateWithoutBackStack(screen: Screen, useBackAnimation: Boolean, vararg args: Argument) = viewModelScope.launch(Dispatchers.Default) { if (backStack.value.last() == screen) { backStack.value.removeLast() @@ -125,15 +130,18 @@ class Navigator @AssistedInject constructor( putArgument(it) } + savedStateHandle[USE_BACK_ANIM] = useBackAnimation savedStateHandle[CURRENT_SCREEN] = screen } - fun navigateBack() = viewModelScope.launch(Dispatchers.Default) { - if (canGoBack()) { - savedStateHandle[CURRENT_SCREEN] = backStack.value.last() - backStack.value.removeLast() + fun navigateBack(useBackAnimation: Boolean = true) = + viewModelScope.launch(Dispatchers.Default) { + if (canGoBack()) { + savedStateHandle[USE_BACK_ANIM] = useBackAnimation + savedStateHandle[CURRENT_SCREEN] = backStack.value.last() + backStack.value.removeLast() + } } - } fun canGoBack(): Boolean { return backStack.value.isNotEmpty() @@ -148,7 +156,9 @@ class Navigator @AssistedInject constructor( * * @param screen The [Screen] that represents [content]. * @param enterAnim Enter Animation. + * @param backEnterAnim Enter Animation for navigating back. * @param exitAnim Exit Animation. + * @param backExitAnim Exit Animation for navigating back. * @param content The Screen content to show when [Navigator.currentScreen] equals [screen]. */ @SuppressLint("ComposableNaming") @@ -156,14 +166,16 @@ class Navigator @AssistedInject constructor( fun composable( screen: Screen, enterAnim: EnterTransition = Transitions.FadeTransitionIn, + backEnterAnim: EnterTransition = Transitions.BackSlidingTransitionIn, exitAnim: ExitTransition = Transitions.FadeTransitionOut, + backExitAnim: ExitTransition = Transitions.BackSlidingTransitionOut, content: @Composable () -> Unit ) { AnimatedVisibility( visible = getCurrentScreen().collectAsState().value == screen, - enter = enterAnim, - exit = exitAnim, - label = screen.toString() + enter = if (!useBackAnimation.collectAsState().value) enterAnim else backEnterAnim, + exit = if (!useBackAnimation.collectAsState().value) exitAnim else backExitAnim, + label = "" ) { content() } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt index 140a1ce6..9e417482 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt @@ -21,10 +21,15 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.filled.Done import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.pullrefresh.PullRefreshDefaults +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButtonDefaults @@ -32,6 +37,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -43,32 +49,33 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -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 dagger.hilt.android.lifecycle.withCreationCallback import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.Book -import ua.acclorite.book_story.presentation.Argument -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.Screen import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar +import ua.acclorite.book_story.presentation.components.CustomSnackbar +import ua.acclorite.book_story.presentation.components.GoBackButton +import ua.acclorite.book_story.presentation.data.Argument +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoBackground -import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoChangeCoverBottomSheet -import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoDeleteDialog import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoDescriptionSection -import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoDetailsBottomSheet import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoInfoSection import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoMoreDropDown -import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoMoveDialog import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoStatisticSection +import ua.acclorite.book_story.presentation.screens.book_info.components.change_cover_bottom_sheet.BookInfoChangeCoverBottomSheet +import ua.acclorite.book_story.presentation.screens.book_info.components.details_bottom_sheet.BookInfoDetailsBottomSheet +import ua.acclorite.book_story.presentation.screens.book_info.components.dialog.BookInfoDeleteDialog +import ua.acclorite.book_story.presentation.screens.book_info.components.dialog.BookInfoMoveDialog import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel +import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel @@ -77,12 +84,12 @@ import ua.acclorite.book_story.ui.Transitions import ua.acclorite.book_story.ui.elevation import ua.acclorite.book_story.util.Constants -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable fun BookInfoScreen( - libraryViewModel: LibraryViewModel = hiltViewModel(), - browseViewModel: BrowseViewModel = hiltViewModel(), - historyViewModel: HistoryViewModel = hiltViewModel(), + libraryViewModel: LibraryViewModel, + browseViewModel: BrowseViewModel, + historyViewModel: HistoryViewModel, navigator: Navigator ) { val context = LocalContext.current @@ -104,22 +111,29 @@ fun BookInfoScreen( val state by viewModel.state.collectAsState() val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() val listState = rememberLazyListState() - + val snackbarState = remember { SnackbarHostState() } + val refreshState = rememberPullRefreshState( + refreshing = state.isRefreshing, + refreshThreshold = PullRefreshDefaults.RefreshThreshold + 32.dp, + refreshingOffset = PullRefreshDefaults.RefreshingOffset + 64.dp, + onRefresh = { + viewModel.onEvent( + BookInfoEvent.OnUpdateBook( + refreshList = { + libraryViewModel.onEvent(LibraryEvent.OnLoadList) + historyViewModel.onEvent(HistoryEvent.OnLoadList) + }, + snackbarState, + context + ) + ) + } + ) val firstVisibleItemIndex by remember { derivedStateOf { listState.firstVisibleItemIndex } } LaunchedEffect(Unit) { viewModel.init( - navigator, - failure = { - Toast.makeText( - context, - context.getString( - R.string.file_not_found, - it.takeLast(40) - ), - Toast.LENGTH_LONG - ).show() - } + navigator ) } @@ -153,8 +167,9 @@ fun BookInfoScreen( Scaffold( Modifier .fillMaxSize() - .nestedScroll(scrollBehavior.nestedScrollConnection) - .windowInsetsPadding(WindowInsets.navigationBars), + .windowInsetsPadding(WindowInsets.navigationBars) + .pullRefresh(refreshState) + .nestedScroll(scrollBehavior.nestedScrollConnection), containerColor = MaterialTheme.colorScheme.surface, topBar = { AnimatedTopAppBar( @@ -164,15 +179,7 @@ fun BookInfoScreen( isTopBarScrolled = null, content1NavigationIcon = { - IconButton(onClick = { - navigator.navigateBack() - }) { - Icon( - imageVector = Icons.AutoMirrored.Outlined.ArrowBack, - contentDescription = "Go back", - tint = MaterialTheme.colorScheme.onSurface - ) - } + GoBackButton(navigator = navigator) }, content1Title = { DefaultTransition(visible = firstVisibleItemIndex > 0) { @@ -186,11 +193,37 @@ fun BookInfoScreen( } }, content1Actions = { + IconButton( + enabled = !state.isRefreshing, + onClick = { + viewModel.onEvent( + BookInfoEvent.OnUpdateBook( + refreshList = { + libraryViewModel.onEvent(LibraryEvent.OnLoadList) + historyViewModel.onEvent(HistoryEvent.OnLoadList) + }, + snackbarState, + context + ) + ) + } + ) { + Icon( + imageVector = Icons.Default.Refresh, + contentDescription = stringResource(id = R.string.refresh_book_content_desc), + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + Box { DefaultTransition( visible = !state.editTitle ) { - BookInfoMoreDropDown(viewModel = viewModel) + BookInfoMoreDropDown( + viewModel = viewModel, + snackbarState = snackbarState + ) } androidx.compose.animation.AnimatedVisibility( visible = state.editTitle, @@ -230,6 +263,12 @@ fun BookInfoScreen( } } ) + }, + bottomBar = { + CustomSnackbar( + modifier = Modifier.padding(bottom = 70.dp), + snackbarState = snackbarState + ) } ) { paddingValues -> Box( @@ -246,7 +285,7 @@ fun BookInfoScreen( if (state.book.coverImage != null) { BookInfoBackground( height = paddingValues.calculateTopPadding() + 12.dp + 195.dp, - image = state.book.coverImage!!.asImageBitmap() + image = state.book.coverImage!! ) } @@ -271,48 +310,54 @@ fun BookInfoScreen( } } - if (state.book.file != null) { - FloatingActionButton( - onClick = { - navigator.navigate( - Screen.READER, Argument( - "book", state.book - ) + FloatingActionButton( + onClick = { + navigator.navigate( + Screen.READER, false, Argument( + "book", state.book ) - }, - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(16.dp), - shape = MaterialTheme.shapes.large, - elevation = FloatingActionButtonDefaults.elevation(), - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - content = { - Row(verticalAlignment = Alignment.CenterVertically) { - Spacer(modifier = Modifier.width(13.dp)) - Icon( - imageVector = Icons.Filled.PlayArrow, - contentDescription = "Continue reading", - Modifier.size(24.dp) + ) + }, + shape = MaterialTheme.shapes.large, + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(16.dp), + elevation = FloatingActionButtonDefaults.elevation(), + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + content = { + Row(verticalAlignment = Alignment.CenterVertically) { + Spacer(modifier = Modifier.width(13.dp)) + Icon( + imageVector = Icons.Filled.PlayArrow, + contentDescription = stringResource(id = R.string.continue_reading_content_desc), + Modifier.size(24.dp) + ) + AnimatedVisibility( + visible = !listState.canScrollBackward + ) { + Text( + text = stringResource( + if (state.book.progress == 0f) R.string.start + else R.string.continue_read + ), + style = MaterialTheme.typography.labelLarge, + maxLines = 1, + modifier = Modifier.padding(start = 8.dp) ) - AnimatedVisibility( - visible = !listState.canScrollBackward - ) { - Text( - text = stringResource( - if (state.book.progress == 0f) R.string.start - else R.string.continue_read - ), - style = MaterialTheme.typography.labelLarge, - maxLines = 1, - modifier = Modifier.padding(start = 8.dp) - ) - } - Spacer(modifier = Modifier.width(16.dp)) } + Spacer(modifier = Modifier.width(16.dp)) } - ) - } + } + ) + + PullRefreshIndicator( + state.isRefreshing, + refreshState, + Modifier.align(Alignment.TopCenter), + backgroundColor = MaterialTheme.colorScheme.inverseSurface, + contentColor = MaterialTheme.colorScheme.inverseOnSurface + ) } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoBackground.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoBackground.kt index 31d3dc3f..722e60ed 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoBackground.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoBackground.kt @@ -15,6 +15,9 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +/** + * Background of the BookInfoScreen. + */ @Composable fun BookInfoBackground(height: Dp, image: ImageBitmap) { val background = MaterialTheme.colorScheme.background @@ -34,7 +37,7 @@ fun BookInfoBackground(height: Dp, image: ImageBitmap) { ) ) } - .blur(2.dp), + .blur(3.dp), alpha = 0.3f, contentScale = ContentScale.Crop ) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDescriptionSection.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDescriptionSection.kt index f4f4ab4b..8c06c645 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDescriptionSection.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDescriptionSection.kt @@ -14,12 +14,15 @@ import androidx.compose.ui.unit.dp import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.Book +/** + * Description section. + */ @Composable fun BookInfoDescriptionSection(book: Book) { SelectionContainer { Text( - book.description - ?: stringResource(id = R.string.error_no_description), + if (book.description?.isNotBlank() == true) book.description + else stringResource(id = R.string.error_no_description), style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurface, textAlign = TextAlign.Start, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoInfoSection.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoInfoSection.kt index 61c385a9..06af60bf 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoInfoSection.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoInfoSection.kt @@ -34,7 +34,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.text.TextStyle @@ -46,6 +45,9 @@ import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel import ua.acclorite.book_story.ui.elevation +/** + * Info section. + */ @OptIn(ExperimentalFoundationApi::class) @Composable fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) { @@ -84,7 +86,7 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) { if (book.coverImage != null) { Image( - bitmap = book.coverImage.asImageBitmap(), + bitmap = book.coverImage, contentDescription = "Cover", modifier = Modifier .fillMaxSize() diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoMoreDropDown.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoMoreDropDown.kt index 756cac9e..61f1ef0a 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoMoreDropDown.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoMoreDropDown.kt @@ -11,6 +11,7 @@ import androidx.compose.material3.DropdownMenu import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -23,8 +24,11 @@ import ua.acclorite.book_story.presentation.components.CustomDropDownMenuItem import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel +/** + * Book info more dropdown. Has three options: Delete, Move and Details. + */ @Composable -fun BookInfoMoreDropDown(viewModel: BookInfoViewModel) { +fun BookInfoMoreDropDown(viewModel: BookInfoViewModel, snackbarState: SnackbarHostState) { val state by viewModel.state.collectAsState() Box { @@ -64,6 +68,7 @@ fun BookInfoMoreDropDown(viewModel: BookInfoViewModel) { leadingIcon = Icons.Outlined.Info, text = stringResource(id = R.string.details_book) ) { + snackbarState.currentSnackbarData?.dismiss() viewModel.onEvent(BookInfoEvent.OnShowHideDetailsBottomSheet) viewModel.onEvent(BookInfoEvent.OnShowHideMoreDropDown) } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoStatisticSection.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoStatisticSection.kt index a08305bb..4d0410ba 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoStatisticSection.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoStatisticSection.kt @@ -19,6 +19,9 @@ import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.presentation.data.removeDigits import ua.acclorite.book_story.presentation.data.removeTrailingZero +/** + * Statistic section. + */ @Composable fun BookInfoStatisticSection(book: Book) { Column( @@ -41,16 +44,16 @@ fun BookInfoStatisticSection(book: Book) { LinearProgressIndicator( book.progress.coerceAtLeast(0.01f), - strokeCap = StrokeCap.Round, modifier = Modifier .fillMaxWidth(), color = MaterialTheme.colorScheme.primary, - trackColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(0.05f) + trackColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(0.05f), + strokeCap = StrokeCap.Round, ) Spacer(modifier = Modifier.height(6.dp)) Text( - text = stringResource( + text = if (book.progress < 1f) stringResource( id = R.string.you_read_query, (book.progress * 100) .toDouble() @@ -59,7 +62,7 @@ fun BookInfoStatisticSection(book: Book) { ) + " " + stringResource( if (book.progress > 0.2f) R.string.read_keep else R.string.read_more - ), + ) else stringResource(id = R.string.read_done), color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodySmall, maxLines = 1, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoChangeCoverBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/change_cover_bottom_sheet/BookInfoChangeCoverBottomSheet.kt similarity index 97% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoChangeCoverBottomSheet.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/change_cover_bottom_sheet/BookInfoChangeCoverBottomSheet.kt index 5e6e4eca..072a1d45 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoChangeCoverBottomSheet.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/change_cover_bottom_sheet/BookInfoChangeCoverBottomSheet.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.book_info.components +package ua.acclorite.book_story.presentation.screens.book_info.components.change_cover_bottom_sheet import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult @@ -32,6 +32,9 @@ import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewMode import ua.acclorite.book_story.ui.ElevationDefaults import ua.acclorite.book_story.ui.elevation +/** + * Change cover bottom sheet. Lets user select photo from the gallery and replaces old one. + */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun BookInfoChangeCoverBottomSheet( diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoChangeCoverBottomSheetItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/change_cover_bottom_sheet/BookInfoChangeCoverBottomSheetItem.kt similarity index 96% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoChangeCoverBottomSheetItem.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/change_cover_bottom_sheet/BookInfoChangeCoverBottomSheetItem.kt index 7778d6a9..bbd0080e 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoChangeCoverBottomSheetItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/change_cover_bottom_sheet/BookInfoChangeCoverBottomSheetItem.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.book_info.components +package ua.acclorite.book_story.presentation.screens.book_info.components.change_cover_bottom_sheet import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -21,6 +21,9 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +/** + * Change cover bottom sheet item. + */ @Composable fun BookInfoChangeCoverBottomSheetItem( icon: ImageVector, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDetailsBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheet.kt similarity index 97% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDetailsBottomSheet.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheet.kt index 6a460b8a..7797eab8 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDetailsBottomSheet.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheet.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.book_info.components +package ua.acclorite.book_story.presentation.screens.book_info.components.details_bottom_sheet import android.widget.Toast import androidx.compose.foundation.layout.Spacer @@ -25,6 +25,9 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +/** + * Details bottom sheet. Displays name, path, last opened and size of the file. + */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun BookInfoDetailsBottomSheet( diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDetailsBottomSheetItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheetItem.kt similarity index 96% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDetailsBottomSheetItem.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheetItem.kt index 662ff4d3..22cf689f 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDetailsBottomSheetItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheetItem.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.book_info.components +package ua.acclorite.book_story.presentation.screens.book_info.components.details_bottom_sheet import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable @@ -18,6 +18,9 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +/** + * Details bottom sheet item. + */ @OptIn(ExperimentalFoundationApi::class) @Composable fun BookInfoDetailsBottomSheetItem( @@ -51,7 +54,6 @@ fun BookInfoDetailsBottomSheetItem( Spacer(modifier = Modifier.height(2.dp)) Text( description, - maxLines = 3, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDeleteDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/dialog/BookInfoDeleteDialog.kt similarity index 91% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDeleteDialog.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/dialog/BookInfoDeleteDialog.kt index 84921428..e395d483 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoDeleteDialog.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/dialog/BookInfoDeleteDialog.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.book_info.components +package ua.acclorite.book_story.presentation.screens.book_info.components.dialog import android.widget.Toast import androidx.compose.material.icons.Icons @@ -7,8 +7,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.components.CustomDialogWithContent +import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent +import ua.acclorite.book_story.presentation.data.Navigator import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent @@ -18,6 +18,9 @@ import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewMode import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel +/** + * Delete dialog. Deletes current book. + */ @Composable fun BookInfoDeleteDialog( libraryViewModel: LibraryViewModel, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoMoveDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/dialog/BookInfoMoveDialog.kt similarity index 90% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoMoveDialog.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/dialog/BookInfoMoveDialog.kt index 92411f5d..12355b58 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoMoveDialog.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/dialog/BookInfoMoveDialog.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.book_info.components +package ua.acclorite.book_story.presentation.screens.book_info.components.dialog import android.widget.Toast import androidx.compose.foundation.lazy.items @@ -13,14 +13,17 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.Category -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.components.CustomDialogWithLazyColumn -import ua.acclorite.book_story.presentation.components.SelectableDialogItem +import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithLazyColumn +import ua.acclorite.book_story.presentation.components.custom_dialog.SelectableDialogItem +import ua.acclorite.book_story.presentation.data.Navigator import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel +/** + * Move dialog. Moves current book to the selected category. + */ @Composable fun BookInfoMoveDialog( libraryViewModel: LibraryViewModel, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt index dce99736..f30828d2 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt @@ -2,9 +2,10 @@ package ua.acclorite.book_story.presentation.screens.book_info.data import android.content.Context import android.net.Uri +import androidx.compose.material3.SnackbarHostState import androidx.compose.ui.focus.FocusRequester import ua.acclorite.book_story.domain.model.Category -import ua.acclorite.book_story.presentation.Navigator +import ua.acclorite.book_story.presentation.data.Navigator sealed class BookInfoEvent { data object OnShowHideChangeCoverBottomSheet : BookInfoEvent() @@ -33,4 +34,18 @@ sealed class BookInfoEvent { val text: String, val success: () -> Unit ) : BookInfoEvent() + + data class OnShowSnackbar( + val text: String, + val action: String?, + val onAction: () -> Unit = {}, + val durationMillis: Long, + val snackbarState: SnackbarHostState + ) : BookInfoEvent() + + data class OnUpdateBook( + val refreshList: () -> Unit, + val snackbarState: SnackbarHostState, + val context: Context + ) : BookInfoEvent() } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt index 7c580f2b..9e6f8a28 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt @@ -7,6 +7,7 @@ import ua.acclorite.book_story.domain.model.Category @Immutable data class BookInfoState( val book: Book, + val isRefreshing: Boolean = false, val editTitle: Boolean = false, val hasFocused: Boolean = false, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt index 559cd40b..48511fe1 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt @@ -5,6 +5,8 @@ import android.content.ClipboardManager import android.content.Context.CLIPBOARD_SERVICE import android.graphics.BitmapFactory import android.os.Build +import androidx.compose.material3.SnackbarResult +import androidx.compose.ui.graphics.asImageBitmap import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.assisted.Assisted @@ -12,28 +14,36 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.Category +import ua.acclorite.book_story.domain.model.NullableBook import ua.acclorite.book_story.domain.use_case.DeleteBooks +import ua.acclorite.book_story.domain.use_case.GetBookFromFile import ua.acclorite.book_story.domain.use_case.UpdateBooks -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.Screen +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.data.Screen @HiltViewModel(assistedFactory = BookInfoViewModel.Factory::class) class BookInfoViewModel @AssistedInject constructor( @Assisted book: Book, private val updateBooks: UpdateBooks, - private val deleteBooks: DeleteBooks + private val deleteBooks: DeleteBooks, + private val getBookFromFile: GetBookFromFile ) : ViewModel() { private val _state = MutableStateFlow(BookInfoState(book)) val state = _state.asStateFlow() + var job: Job? = null + fun onEvent(event: BookInfoEvent) { when (event) { is BookInfoEvent.OnShowHideChangeCoverBottomSheet -> { @@ -47,9 +57,11 @@ class BookInfoViewModel @AssistedInject constructor( is BookInfoEvent.OnChangeCover -> { viewModelScope.launch { val image = event.context.contentResolver?.openInputStream(event.uri)?.use { - BitmapFactory.decodeStream(it) + BitmapFactory.decodeStream(it).asImageBitmap() } ?: return@launch + image.prepareToDraw() + updateBooks.execute( listOf( _state.value.book.copy(coverImage = image) @@ -221,7 +233,7 @@ class BookInfoViewModel @AssistedInject constructor( it != _state.value.selectedCategory }.size - 1 ) - event.navigator.navigate(Screen.LIBRARY) + event.navigator.navigate(Screen.LIBRARY, true) } } @@ -243,10 +255,139 @@ class BookInfoViewModel @AssistedInject constructor( event.success() } } + + is BookInfoEvent.OnShowSnackbar -> { + viewModelScope.launch { + job?.cancel() + event.snackbarState.currentSnackbarData?.dismiss() + + if (event.durationMillis > 0) { + job = viewModelScope.launch(Dispatchers.IO) { + delay(event.durationMillis) + event.snackbarState.currentSnackbarData?.dismiss() + } + } + + val snackbar = event.snackbarState.showSnackbar( + event.text, + actionLabel = event.action + ) + + when (snackbar) { + SnackbarResult.Dismissed -> Unit + SnackbarResult.ActionPerformed -> { + event.onAction() + } + } + } + } + + is BookInfoEvent.OnUpdateBook -> { + viewModelScope.launch(Dispatchers.IO) { + _state.update { + it.copy( + isRefreshing = true + ) + } + + if (_state.value.book.file == null) { + onEvent( + BookInfoEvent.OnShowSnackbar( + text = event.context.getString( + R.string.file_not_found, + _state.value.book.filePath + .substringAfterLast("/") + .takeLast(25) + ), + action = event.context.getString(R.string.retry), + onAction = { + onEvent( + BookInfoEvent.OnUpdateBook( + refreshList = event.refreshList, + snackbarState = event.snackbarState, + context = event.context + ) + ) + }, + durationMillis = 4000L, + snackbarState = event.snackbarState + ) + ) + delay(500) + _state.update { + it.copy( + isRefreshing = false + ) + } + return@launch + } + + val nullableBook = getBookFromFile.execute(_state.value.book.file!!) + + if (nullableBook is NullableBook.Null) { + onEvent( + BookInfoEvent.OnShowSnackbar( + text = nullableBook.message?.asString(event.context) + ?: event.context.getString(R.string.error_something_went_wrong), + action = event.context.getString(R.string.retry), + onAction = { + onEvent( + BookInfoEvent.OnUpdateBook( + refreshList = event.refreshList, + snackbarState = event.snackbarState, + context = event.context + ) + ) + }, + durationMillis = 4000L, + snackbarState = event.snackbarState + ) + ) + + delay(500) + _state.update { + it.copy( + isRefreshing = false + ) + } + return@launch + } + + val updatedBook = nullableBook.book?.first ?: return@launch + + _state.update { + it.copy( + book = it.book.copy( + author = updatedBook.author, + description = updatedBook.description, + text = updatedBook.text + ) + ) + } + updateBooks.execute(listOf(_state.value.book)) + event.refreshList() + + onEvent( + BookInfoEvent.OnShowSnackbar( + event.context.getString(R.string.book_updated), + action = null, + durationMillis = 4000L, + snackbarState = event.snackbarState + ) + ) + + delay(500) + _state.update { + it.copy( + isRefreshing = false + ) + } + } + } } } - fun init(navigator: Navigator, failure: (String) -> Unit) { + fun init(navigator: Navigator) { viewModelScope.launch { val book = navigator.retrieveArgument("book") as? Book @@ -255,10 +396,6 @@ class BookInfoViewModel @AssistedInject constructor( return@launch } - if (book.file == null) { - failure(book.filePath) - } - _state.update { BookInfoState(book = book) } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/BrowseScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/BrowseScreen.kt index ca5d96ab..36f6a1fa 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/BrowseScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/BrowseScreen.kt @@ -53,24 +53,24 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberPermissionState import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.Screen import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar -import ua.acclorite.book_story.presentation.components.IsEmpty -import ua.acclorite.book_story.presentation.components.IsError import ua.acclorite.book_story.presentation.components.MoreDropDown -import ua.acclorite.book_story.presentation.screens.browse.components.BrowseAddingDialog +import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty +import ua.acclorite.book_story.presentation.components.is_messages.IsError +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.presentation.screens.browse.components.BrowseFileItem import ua.acclorite.book_story.presentation.screens.browse.components.BrowseStoragePermissionDialog +import ua.acclorite.book_story.presentation.screens.browse.components.adding_dialog.BrowseAddingDialog import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel +import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel import ua.acclorite.book_story.ui.DefaultTransition import ua.acclorite.book_story.ui.Transitions import ua.acclorite.book_story.ui.elevation @@ -83,7 +83,8 @@ import ua.acclorite.book_story.ui.elevation ) @Composable fun BrowseScreen( - viewModel: BrowseViewModel = hiltViewModel(), + viewModel: BrowseViewModel, + libraryViewModel: LibraryViewModel, navigator: Navigator ) { val permissionState = rememberPermissionState( @@ -127,7 +128,11 @@ fun BrowseScreen( BrowseStoragePermissionDialog(viewModel, permissionState) } if (state.showAddingDialog) { - BrowseAddingDialog(viewModel = viewModel, navigator = navigator) + BrowseAddingDialog( + viewModel = viewModel, + navigator = navigator, + libraryViewModel = libraryViewModel + ) } Scaffold( @@ -270,13 +275,14 @@ fun BrowseScreen( columns = GridCells.Adaptive(170.dp), contentPadding = PaddingValues(12.dp) ) { - items(state.selectableFiles, key = { it.first.path }) { selectableFile -> + items( + state.selectableFiles, + key = { it.first.path } + ) { selectableFile -> BrowseFileItem( file = selectableFile, modifier = Modifier - .animateItemPlacement( - animationSpec = tween(300) - ), + .animateItemPlacement(), onClick = { viewModel.onEvent(BrowseEvent.OnSelectFile(selectableFile)) } @@ -317,7 +323,7 @@ fun BrowseScreen( message = stringResource(id = R.string.browse_empty), icon = painterResource(id = R.drawable.empty_browse), actionTitle = stringResource(id = R.string.get_help), - action = { navigator.navigate(Screen.HELP) } + action = { navigator.navigate(Screen.HELP, false) } ) } @@ -325,9 +331,8 @@ fun BrowseScreen( state.isRefreshing, refreshState, Modifier.align(Alignment.TopCenter), - backgroundColor = MaterialTheme.elevation(), - contentColor = MaterialTheme.colorScheme.primary, - scale = true + backgroundColor = MaterialTheme.colorScheme.inverseSurface, + contentColor = MaterialTheme.colorScheme.inverseOnSurface ) } } @@ -343,7 +348,7 @@ fun BrowseScreen( return@BackHandler } - navigator.navigate(Screen.LIBRARY) + navigator.navigate(Screen.LIBRARY, false) } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseFIleItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseFIleItem.kt index fd538230..af035842 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseFIleItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseFIleItem.kt @@ -37,6 +37,9 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +/** + * Browse list element item. Can be selected. + */ @Composable fun BrowseFileItem(file: Pair, modifier: Modifier, onClick: () -> Unit) { val fileExtension: String = file.first.name.substringAfterLast(".", "") diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseStoragePermissionDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseStoragePermissionDialog.kt index 2dddfea9..887ac8cb 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseStoragePermissionDialog.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseStoragePermissionDialog.kt @@ -10,10 +10,13 @@ import androidx.compose.ui.res.stringResource import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionState import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.components.CustomDialogWithContent +import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel +/** + * Storage permission dialog. + */ @OptIn(ExperimentalPermissionsApi::class) @Composable fun BrowseStoragePermissionDialog(viewModel: BrowseViewModel, permissionState: PermissionState) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseAddingDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialog.kt similarity index 77% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseAddingDialog.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialog.kt index 92cef92b..6e50c3c1 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseAddingDialog.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialog.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.browse.components +package ua.acclorite.book_story.presentation.screens.browse.components.adding_dialog import android.widget.Toast import androidx.compose.foundation.layout.Box @@ -19,18 +19,21 @@ import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.components.CustomDialogWithLazyColumn +import ua.acclorite.book_story.domain.model.NullableBook +import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithLazyColumn +import ua.acclorite.book_story.presentation.data.Navigator import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel +/** + * Adding dialog. Adds all selected books to the Library. + */ @Composable fun BrowseAddingDialog( - libraryViewModel: LibraryViewModel = hiltViewModel(), + libraryViewModel: LibraryViewModel, viewModel: BrowseViewModel, navigator: Navigator ) { @@ -42,7 +45,10 @@ fun BrowseAddingDialog( imageVectorIcon = Icons.Default.AddChart, description = stringResource(id = R.string.add_books_description), actionText = stringResource(id = R.string.add), - isActionEnabled = !state.isBooksLoading, + isActionEnabled = !state.isBooksLoading && + state.selectedBooks + .filterIsInstance() + .isNotEmpty(), onDismiss = { viewModel.onEvent(BrowseEvent.OnAddingDialogDismiss) }, onAction = { viewModel.onEvent( @@ -80,7 +86,15 @@ fun BrowseAddingDialog( } else { items(state.selectedBooks) { book -> BrowseAddingDialogItem(result = book) { - viewModel.onEvent(BrowseEvent.OnSelectBook(book)) + if (it) { + viewModel.onEvent(BrowseEvent.OnSelectBook(book)) + } else { + Toast.makeText( + context, + book.message?.asString(context), + Toast.LENGTH_LONG + ).show() + } } } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseAddingDialogItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt similarity index 81% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseAddingDialogItem.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt index 55776d10..9bfd271c 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseAddingDialogItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.browse.components +package ua.acclorite.book_story.presentation.screens.browse.components.adding_dialog import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -10,27 +10,32 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Warning +import androidx.compose.material.icons.filled.Error +import androidx.compose.material.icons.filled.Lock import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.NullableBook import ua.acclorite.book_story.presentation.components.CustomCheckbox +/** + * Adding dialog item. + */ @Composable -fun BrowseAddingDialogItem(result: NullableBook, onClick: () -> Unit) { - +fun BrowseAddingDialogItem(result: NullableBook, onClick: (Boolean) -> Unit) { if (result is NullableBook.NotNull) { Row( Modifier .fillMaxWidth() .clickable { - onClick() + onClick(true) } .padding(vertical = 12.dp, horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically @@ -58,14 +63,24 @@ fun BrowseAddingDialogItem(result: NullableBook, onClick: () -> Unit) { } } } else { + val icon = + if (result.message?.asString() == stringResource(R.string.error_file_encrypted)) { + Icons.Default.Lock + } else { + Icons.Default.Error + } + Row( Modifier .fillMaxWidth() + .clickable { + onClick(false) + } .padding(vertical = 12.dp, horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( - imageVector = Icons.Default.Warning, + imageVector = icon, contentDescription = "Error", modifier = Modifier.size(26.dp), tint = MaterialTheme.colorScheme.error diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseEvent.kt index c5ca39a8..9992bd75 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseEvent.kt @@ -7,7 +7,7 @@ import androidx.compose.ui.focus.FocusRequester import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionState import ua.acclorite.book_story.domain.model.NullableBook -import ua.acclorite.book_story.presentation.Navigator +import ua.acclorite.book_story.presentation.data.Navigator import java.io.File sealed class BrowseEvent { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseViewModel.kt index 62db5c49..bd4e0df8 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseViewModel.kt @@ -22,8 +22,8 @@ import ua.acclorite.book_story.domain.use_case.FastGetBooks import ua.acclorite.book_story.domain.use_case.GetBooksFromFiles import ua.acclorite.book_story.domain.use_case.GetFilesFromDownloads import ua.acclorite.book_story.domain.use_case.InsertBooks -import ua.acclorite.book_story.presentation.Argument -import ua.acclorite.book_story.presentation.Screen +import ua.acclorite.book_story.presentation.data.Argument +import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.util.Resource import javax.inject.Inject @@ -42,7 +42,14 @@ class BrowseViewModel @Inject constructor( private var job: Job? = null init { - onEvent(BrowseEvent.OnLoadList) + viewModelScope.launch(Dispatchers.IO) { + _state.update { + it.copy( + isLoading = true + ) + } + getFilesFromDownloads() + } } fun onEvent(event: BrowseEvent) { @@ -268,34 +275,23 @@ class BrowseViewModel @Inject constructor( is BrowseEvent.OnGetBooksFromFiles -> { viewModelScope.launch(Dispatchers.IO) { - getBooksFromFiles.execute( + _state.update { + it.copy( + isBooksLoading = true + ) + } + + val books = getBooksFromFiles.execute( _state.value.selectableFiles .filter { it.second } .map { it.first } - ).collect { result -> - when (result) { - is Resource.Success -> { - if (result.data?.isEmpty() ?: return@collect) { - return@collect - } - _state.update { - it.copy( - selectedBooks = result.data, - isBooksLoading = false - ) - } - } + ) - is Resource.Loading -> { - _state.update { - it.copy( - isBooksLoading = result.isLoading - ) - } - } - - is Resource.Error -> Unit - } + _state.update { + it.copy( + selectedBooks = books, + isBooksLoading = false + ) } } } @@ -323,7 +319,11 @@ class BrowseViewModel @Inject constructor( onEvent(BrowseEvent.OnClearSelectedFiles) onEvent(BrowseEvent.OnLoadList) - event.navigator.navigate(Screen.LIBRARY, Argument("added_books", books)) + event.navigator.navigate( + Screen.LIBRARY, + false, + Argument("added_books", books) + ) } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/HistoryScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/HistoryScreen.kt index 7d6740bb..c8973853 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/HistoryScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/HistoryScreen.kt @@ -54,23 +54,24 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.Argument -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.Screen import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar import ua.acclorite.book_story.presentation.components.CategoryTitle import ua.acclorite.book_story.presentation.components.CustomSnackbar -import ua.acclorite.book_story.presentation.components.IsEmpty import ua.acclorite.book_story.presentation.components.MoreDropDown +import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty +import ua.acclorite.book_story.presentation.data.Argument +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.presentation.screens.history.components.HistoryDeleteWholeHistoryDialog import ua.acclorite.book_story.presentation.screens.history.components.HistoryItem import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel +import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent +import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel import ua.acclorite.book_story.ui.Transitions import ua.acclorite.book_story.ui.elevation import java.util.UUID @@ -81,11 +82,14 @@ import java.util.UUID ) @Composable fun HistoryScreen( - viewModel: HistoryViewModel = hiltViewModel(), + viewModel: HistoryViewModel, + libraryViewModel: LibraryViewModel, navigator: Navigator ) { val context = LocalContext.current val state by viewModel.state.collectAsState() + val books = libraryViewModel.state.collectAsState().value.books.map { it.first } + val refreshState = rememberPullRefreshState( refreshing = state.isRefreshing, onRefresh = { @@ -118,7 +122,7 @@ fun HistoryScreen( } if (state.showDeleteWholeHistoryDialog) { - HistoryDeleteWholeHistoryDialog(viewModel = viewModel) + HistoryDeleteWholeHistoryDialog(viewModel = viewModel, libraryViewModel = libraryViewModel) } Scaffold( @@ -144,7 +148,10 @@ fun HistoryScreen( ) }, content1Actions = { - IconButton(onClick = { viewModel.onEvent(HistoryEvent.OnSearchShowHide) }) { + IconButton( + enabled = !state.isRefreshing, + onClick = { viewModel.onEvent(HistoryEvent.OnSearchShowHide) } + ) { Icon( imageVector = Icons.Default.Search, contentDescription = "Search history", @@ -153,7 +160,9 @@ fun HistoryScreen( ) } IconButton( - enabled = !state.isLoading && !state.isRefreshing && state.history.isNotEmpty(), + enabled = !state.isLoading + && !state.isRefreshing + && state.history.isNotEmpty(), onClick = { viewModel.onEvent(HistoryEvent.OnShowHideDeleteWholeHistoryDialog) } @@ -248,7 +257,7 @@ fun HistoryScreen( } CategoryTitle( - modifier = Modifier.animateItemPlacement(tween(300)), + modifier = Modifier.animateItemPlacement(), title = when (groupedHistory.title) { "today" -> stringResource(id = R.string.today) "yesterday" -> stringResource(id = R.string.yesterday) @@ -262,27 +271,36 @@ fun HistoryScreen( items( groupedHistory.history, key = { it.id ?: UUID.randomUUID() } ) { + val book = books.find { book -> book.id == it.bookId } ?: return@items + HistoryItem( - modifier = Modifier.animateItemPlacement(tween(300)), + modifier = Modifier.animateItemPlacement(), history = it, + book = book, onBodyClick = { navigator.navigate( Screen.BOOK_INFO, - Argument("book", it.book) + false, + Argument("book", book) ) }, onTitleClick = { navigator.navigate( Screen.READER, - Argument("book", it.book) + false, + Argument("book", book) ) }, + isDeleteEnabled = !state.isRefreshing, onDeleteClick = { viewModel.onEvent( HistoryEvent.OnDeleteHistoryElement( - it, - snackbarState, - context + historyToDelete = it, + snackbarState = snackbarState, + context = context, + refreshList = { + libraryViewModel.onEvent(LibraryEvent.OnLoadList) + } ) ) } @@ -318,9 +336,8 @@ fun HistoryScreen( state.isRefreshing, refreshState, Modifier.align(Alignment.TopCenter), - backgroundColor = MaterialTheme.elevation(), - contentColor = MaterialTheme.colorScheme.primary, - scale = true + backgroundColor = MaterialTheme.colorScheme.inverseSurface, + contentColor = MaterialTheme.colorScheme.inverseOnSurface ) } } @@ -331,7 +348,7 @@ fun HistoryScreen( return@BackHandler } - navigator.navigate(Screen.LIBRARY) + navigator.navigate(Screen.LIBRARY, false) } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/components/HistoryDeleteWholeHistoryDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/components/HistoryDeleteWholeHistoryDialog.kt index 0474860f..a71b7afa 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/components/HistoryDeleteWholeHistoryDialog.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/components/HistoryDeleteWholeHistoryDialog.kt @@ -7,13 +7,19 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.components.CustomDialogWithContent +import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel +import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent +import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel +/** + * Delete whole history dialog. + */ @Composable fun HistoryDeleteWholeHistoryDialog( - viewModel: HistoryViewModel + viewModel: HistoryViewModel, + libraryViewModel: LibraryViewModel ) { val context = LocalContext.current @@ -29,7 +35,9 @@ fun HistoryDeleteWholeHistoryDialog( isActionEnabled = true, onAction = { viewModel.onEvent( - HistoryEvent.OnDeleteWholeHistory + HistoryEvent.OnDeleteWholeHistory { + libraryViewModel.onEvent(LibraryEvent.OnLoadList) + } ) Toast.makeText( context, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/components/HistoryItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/components/HistoryItem.kt index f1cb83f0..b890e7a7 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/components/HistoryItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/components/HistoryItem.kt @@ -26,22 +26,29 @@ import androidx.compose.runtime.Composable 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 ua.acclorite.book_story.R +import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.History import ua.acclorite.book_story.ui.elevation import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +/** + * History list element item. + */ @Composable fun HistoryItem( modifier: Modifier = Modifier, history: History, + book: Book, onBodyClick: () -> Unit, onTitleClick: () -> Unit, + isDeleteEnabled: Boolean, onDeleteClick: () -> Unit ) { val date = Date(history.time) @@ -77,10 +84,10 @@ fun HistoryItem( tint = MaterialTheme.elevation(12.dp) ) - if (history.book.coverImage != null) { + if (book.coverImage != null) { Image( - bitmap = history.book.coverImage.asImageBitmap(), - contentDescription = "Cover", + bitmap = book.coverImage, + contentDescription = stringResource(id = R.string.cover_image_content_desc), modifier = Modifier .fillMaxSize() .clip(MaterialTheme.shapes.medium), @@ -96,7 +103,7 @@ fun HistoryItem( modifier = Modifier.fillMaxHeight() ) { Text( - history.book.title, + book.title, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyLarge, modifier = Modifier @@ -109,7 +116,7 @@ fun HistoryItem( maxLines = 2, overflow = TextOverflow.Ellipsis ) - Spacer(modifier = Modifier.height(4.dp)) + Spacer(modifier = Modifier.height(2.dp)) Text( pattern.format(date), color = MaterialTheme.colorScheme.onSurfaceVariant, @@ -123,6 +130,7 @@ fun HistoryItem( } Box(modifier = Modifier.weight(0.1f), contentAlignment = Alignment.CenterEnd) { IconButton( + enabled = isDeleteEnabled, onClick = { onDeleteClick() } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryEvent.kt index ff091295..668978bd 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryEvent.kt @@ -9,10 +9,11 @@ sealed class HistoryEvent { data object OnRefreshList : HistoryEvent() data object OnLoadList : HistoryEvent() data object OnShowHideDeleteWholeHistoryDialog : HistoryEvent() - data object OnDeleteWholeHistory : HistoryEvent() + data class OnDeleteWholeHistory(val refreshList: () -> Unit) : HistoryEvent() data class OnDeleteHistoryElement( val historyToDelete: History, val snackbarState: SnackbarHostState, + val refreshList: () -> Unit, val context: Context ) : HistoryEvent() diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryViewModel.kt index 8217099f..055a05d4 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryViewModel.kt @@ -1,6 +1,5 @@ package ua.acclorite.book_story.presentation.screens.history.data -import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarResult import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -17,6 +16,7 @@ import ua.acclorite.book_story.domain.model.GroupedHistory import ua.acclorite.book_story.domain.model.History import ua.acclorite.book_story.domain.use_case.DeleteHistory import ua.acclorite.book_story.domain.use_case.DeleteWholeHistory +import ua.acclorite.book_story.domain.use_case.GetBooksById import ua.acclorite.book_story.domain.use_case.GetHistory import ua.acclorite.book_story.domain.use_case.InsertHistory import ua.acclorite.book_story.util.Resource @@ -30,16 +30,25 @@ class HistoryViewModel @Inject constructor( private val insertHistory: InsertHistory, private val getHistory: GetHistory, private val deleteWholeHistory: DeleteWholeHistory, - private val deleteHistory: DeleteHistory + private val deleteHistory: DeleteHistory, + private val getBooksById: GetBooksById ) : ViewModel() { private val _state = MutableStateFlow(HistoryState()) val state = _state.asStateFlow() private var job: Job? = null + private var job2: Job? = null init { - onEvent(HistoryEvent.OnLoadList) + viewModelScope.launch(Dispatchers.IO) { + _state.update { + it.copy( + isLoading = true + ) + } + getHistoryFromDatabase() + } } fun onEvent(event: HistoryEvent) { @@ -85,6 +94,7 @@ class HistoryViewModel @Inject constructor( } deleteWholeHistory.execute() + event.refreshList() getHistoryFromDatabase("") } @@ -104,20 +114,27 @@ class HistoryViewModel @Inject constructor( listOf(event.historyToDelete) ) onEvent(HistoryEvent.OnRefreshList) + event.refreshList() + job2?.cancel() event.snackbarState.currentSnackbarData?.dismiss() + + job2 = viewModelScope.launch(Dispatchers.IO) { + delay(10000) + event.snackbarState.currentSnackbarData?.dismiss() + } val snackbar = event.snackbarState.showSnackbar( event.context.getString(R.string.history_element_deleted), - event.context.getString(R.string.undo), - duration = SnackbarDuration.Long + event.context.getString(R.string.undo) ) when (snackbar) { - SnackbarResult.Dismissed -> return@launch + SnackbarResult.Dismissed -> Unit SnackbarResult.ActionPerformed -> { insertHistory.execute( listOf(event.historyToDelete) ) + event.refreshList() onEvent(HistoryEvent.OnRefreshList) } } @@ -197,7 +214,7 @@ class HistoryViewModel @Inject constructor( } fun filterMaxElementsById(elements: List): List { - val groupedById = elements.groupBy { it.book.id } + val groupedById = elements.groupBy { it.bookId } val maxElementsById = groupedById.map { (_, values) -> values.maxByOrNull { it.time } } @@ -223,11 +240,15 @@ class HistoryViewModel @Inject constructor( return@collect } + val books = getBooksById.execute( + history.map { it.bookId }.distinct() + ) val groupedHistory = mutableListOf() history .filter { - it.book.title.lowercase().trim().contains(query.lowercase().trim()) + val book = books.find { book -> book.id == it.bookId }!! + book.title.lowercase().trim().contains(query.lowercase().trim()) }.groupBy { item -> val calendar = Calendar.getInstance().apply { timeInMillis = item.time diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/LibraryScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/LibraryScreen.kt index 3f786611..fa843c82 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/LibraryScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/LibraryScreen.kt @@ -24,7 +24,6 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.CircularProgressIndicator @@ -69,24 +68,23 @@ import androidx.compose.ui.text.input.KeyboardCapitalization 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 kotlinx.coroutines.delay import kotlinx.coroutines.launch import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.Category -import ua.acclorite.book_story.presentation.Argument -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.Screen import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar -import ua.acclorite.book_story.presentation.components.IsEmpty import ua.acclorite.book_story.presentation.components.MoreDropDown +import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty +import ua.acclorite.book_story.presentation.data.Argument +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel import ua.acclorite.book_story.presentation.screens.library.components.LibraryBookItem -import ua.acclorite.book_story.presentation.screens.library.components.LibraryDeleteDialog -import ua.acclorite.book_story.presentation.screens.library.components.LibraryMoveDialog import ua.acclorite.book_story.presentation.screens.library.components.LibraryTabRow +import ua.acclorite.book_story.presentation.screens.library.components.dialog.LibraryDeleteDialog +import ua.acclorite.book_story.presentation.screens.library.components.dialog.LibraryMoveDialog import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel import ua.acclorite.book_story.ui.Transitions @@ -99,15 +97,17 @@ import java.util.UUID ) @Composable fun LibraryScreen( - viewModel: LibraryViewModel = hiltViewModel(), - browseViewModel: BrowseViewModel = hiltViewModel(), - historyViewModel: HistoryViewModel = hiltViewModel(), + viewModel: LibraryViewModel, + browseViewModel: BrowseViewModel, + historyViewModel: HistoryViewModel, navigator: Navigator, addedBooks: List ) { val state by viewModel.state.collectAsState() - val pagerState = - rememberPagerState(initialPage = state.currentPage, pageCount = { Category.entries.size }) + val pagerState = rememberPagerState( + initialPage = state.currentPage, + pageCount = { Category.entries.size } + ) val refreshState = rememberPullRefreshState( refreshing = state.isRefreshing, onRefresh = { @@ -165,16 +165,15 @@ fun LibraryScreen( overflow = TextOverflow.Ellipsis, maxLines = 1 ) - Spacer(modifier = Modifier.width(4.dp)) + Spacer(modifier = Modifier.width(6.dp)) Text( text = state.books.size.toString(), modifier = Modifier - .clip(RoundedCornerShape(12.dp)) + .clip(MaterialTheme.shapes.medium) .background(MaterialTheme.elevation(elevation = 6.dp)) - .padding(horizontal = 10.dp, vertical = 4.dp), + .padding(horizontal = 8.dp, vertical = 3.dp), color = MaterialTheme.colorScheme.onSurfaceVariant, - fontSize = 16.sp, - style = MaterialTheme.typography.titleLarge + fontSize = 16.sp ) } }, @@ -313,9 +312,11 @@ fun LibraryScreen( LaunchedEffect(state.books) { categorizedBooks.clear() categorizedBooks.addAll(state.books.filter { it.first.category == category } - .sortedByDescending { - it.first.lastOpened - }) + .sortedWith( + compareByDescending> { it.first.lastOpened } + .thenBy { it.first.title } + ) + ) categoryIsLoading = false } @@ -333,15 +334,14 @@ fun LibraryScreen( ) { LibraryBookItem( book = it, - modifier = Modifier.animateItemPlacement( - animationSpec = tween(300) - ), + modifier = Modifier.animateItemPlacement(), onCoverImageClick = { if (state.hasSelectedItems) { viewModel.onEvent(LibraryEvent.OnSelectBook(it)) } else { navigator.navigate( Screen.BOOK_INFO, + false, Argument("book", it.first) ) } @@ -354,6 +354,7 @@ fun LibraryScreen( onButtonClick = { navigator.navigate( Screen.READER, + false, Argument("book", it.first) ) } @@ -385,7 +386,7 @@ fun LibraryScreen( modifier = Modifier.align(Alignment.Center), actionTitle = stringResource(id = R.string.add_book) ) { - navigator.navigate(Screen.BROWSE) + navigator.navigate(Screen.BROWSE, false) } } @@ -393,9 +394,8 @@ fun LibraryScreen( state.isRefreshing, refreshState, Modifier.align(Alignment.TopCenter), - backgroundColor = MaterialTheme.elevation(), - contentColor = MaterialTheme.colorScheme.primary, - scale = true + backgroundColor = MaterialTheme.colorScheme.inverseSurface, + contentColor = MaterialTheme.colorScheme.inverseOnSurface ) } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryBookItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryBookItem.kt index f0d168c5..a097aafc 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryBookItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryBookItem.kt @@ -18,7 +18,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Image import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material.icons.filled.Warning import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButtonDefaults @@ -30,7 +29,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -40,6 +38,9 @@ import ua.acclorite.book_story.presentation.data.removeDigits import ua.acclorite.book_story.presentation.data.removeTrailingZero import ua.acclorite.book_story.ui.elevation +/** + * Library list element item. + */ @OptIn(ExperimentalFoundationApi::class) @Composable fun LibraryBookItem( @@ -102,7 +103,7 @@ fun LibraryBookItem( if (book.first.coverImage != null) { Image( - bitmap = book.first.coverImage!!.asImageBitmap(), + bitmap = book.first.coverImage!!, contentDescription = "Cover", modifier = Modifier .fillMaxSize() @@ -111,40 +112,25 @@ fun LibraryBookItem( ) } - if (book.first.file != null) { - Text( - "${ - (book.first.progress * 100) - .toDouble() - .removeDigits(1) - .removeTrailingZero() - }%", - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onPrimary, - modifier = Modifier - .padding(6.dp) - .clip(MaterialTheme.shapes.small) - .background(MaterialTheme.colorScheme.primary) - .padding(horizontal = 8.dp, vertical = 4.dp), - style = MaterialTheme.typography.bodySmall - ) - } else { - Icon( - imageVector = Icons.Default.Warning, - contentDescription = "File is not found", - modifier = Modifier - .padding(6.dp) - .clip(MaterialTheme.shapes.small) - .background(MaterialTheme.colorScheme.error) - .padding(4.dp) - .size(20.dp), - tint = MaterialTheme.colorScheme.onError - ) - } + Text( + "${ + (book.first.progress * 100) + .toDouble() + .removeDigits(1) + .removeTrailingZero() + }%", + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onPrimary, + modifier = Modifier + .padding(6.dp) + .clip(MaterialTheme.shapes.small) + .background(MaterialTheme.colorScheme.primary) + .padding(horizontal = 8.dp, vertical = 4.dp), + style = MaterialTheme.typography.bodySmall + ) FilledIconButton( onClick = { onButtonClick() }, - enabled = book.first.file != null, modifier = Modifier .align(Alignment.BottomEnd) .padding(6.dp) @@ -154,11 +140,7 @@ fun LibraryBookItem( containerColor = MaterialTheme.colorScheme.primaryContainer .copy(0.9f), contentColor = MaterialTheme.colorScheme.onPrimaryContainer - .copy(0.9f), - disabledContainerColor = MaterialTheme.colorScheme.primaryContainer - .copy(0.6f), - disabledContentColor = MaterialTheme.colorScheme.onPrimaryContainer - .copy(0.6f) + .copy(0.9f) ) ) { Icon( diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTabRow.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTabRow.kt index 664ce0ee..32cda42e 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTabRow.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTabRow.kt @@ -31,6 +31,9 @@ import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel import ua.acclorite.book_story.ui.elevation +/** + * Tab row, either scrollable or static, depends on screen width. + */ @OptIn(ExperimentalFoundationApi::class) @Composable fun LibraryTabRow(viewModel: LibraryViewModel, books: List, pagerState: PagerState) { @@ -97,9 +100,9 @@ fun LibraryTabRow(viewModel: LibraryViewModel, books: List, pagerState: Pa Text( text = tabItem.second.toString(), modifier = Modifier - .clip(RoundedCornerShape(12.dp)) + .clip(MaterialTheme.shapes.medium) .background(MaterialTheme.elevation(6.dp)) - .padding(horizontal = 6.dp, vertical = 4.dp), + .padding(horizontal = 8.dp, vertical = 4.dp), color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium ) @@ -153,9 +156,9 @@ fun LibraryTabRow(viewModel: LibraryViewModel, books: List, pagerState: Pa Text( text = tabItem.second.toString(), modifier = Modifier - .clip(RoundedCornerShape(12.dp)) + .clip(MaterialTheme.shapes.medium) .background(MaterialTheme.elevation(6.dp)) - .padding(horizontal = 6.dp, vertical = 4.dp), + .padding(horizontal = 8.dp, vertical = 4.dp), color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium ) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryDeleteDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/dialog/LibraryDeleteDialog.kt similarity index 92% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryDeleteDialog.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/dialog/LibraryDeleteDialog.kt index 1ea9df85..5b1117df 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryDeleteDialog.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/dialog/LibraryDeleteDialog.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.library.components +package ua.acclorite.book_story.presentation.screens.library.components.dialog import android.widget.Toast import androidx.compose.material.icons.Icons @@ -9,7 +9,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.components.CustomDialogWithContent +import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent @@ -17,6 +17,9 @@ import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewMode import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel +/** + * Delete dialog. Deletes all selected books. + */ @Composable fun LibraryDeleteDialog( viewModel: LibraryViewModel, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryMoveDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/dialog/LibraryMoveDialog.kt similarity index 90% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryMoveDialog.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/dialog/LibraryMoveDialog.kt index 86e17724..a904a4cb 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryMoveDialog.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/dialog/LibraryMoveDialog.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.library.components +package ua.acclorite.book_story.presentation.screens.library.components.dialog import android.widget.Toast import androidx.compose.foundation.ExperimentalFoundationApi @@ -13,11 +13,14 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.Category -import ua.acclorite.book_story.presentation.components.CustomDialogWithLazyColumn -import ua.acclorite.book_story.presentation.components.SelectableDialogItem +import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithLazyColumn +import ua.acclorite.book_story.presentation.components.custom_dialog.SelectableDialogItem import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel +/** + * Move dialog. Moves all selected books to the selected category. + */ @OptIn(ExperimentalFoundationApi::class) @Composable fun LibraryMoveDialog(viewModel: LibraryViewModel, pagerState: PagerState) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryViewModel.kt index b25724b2..4f0c27a4 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryViewModel.kt @@ -29,10 +29,23 @@ class LibraryViewModel @Inject constructor( private val _state = MutableStateFlow(LibraryState()) val state = _state.asStateFlow() + private val _isReady = MutableStateFlow(false) + val isReady = _isReady.asStateFlow() + private var job: Job? = null init { - onEvent(LibraryEvent.OnLoadList) + viewModelScope.launch(Dispatchers.IO) { + _state.update { + it.copy( + isLoading = true + ) + } + getBooksFromDatabase() + _isReady.update { + true + } + } } fun onEvent(event: LibraryEvent) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt index 2c8d9d18..5459a8f9 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt @@ -1,6 +1,8 @@ package ua.acclorite.book_story.presentation.screens.reader import android.annotation.SuppressLint +import android.os.Build +import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.BackHandler import androidx.activity.viewModels @@ -20,7 +22,6 @@ 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 @@ -50,25 +51,25 @@ 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 dagger.hilt.android.lifecycle.withCreationCallback import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.Book -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.components.IsError +import ua.acclorite.book_story.presentation.components.CustomSelectionContainer +import ua.acclorite.book_story.presentation.components.is_messages.IsError import ua.acclorite.book_story.presentation.data.MainViewModel +import ua.acclorite.book_story.presentation.data.Navigator import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel -import ua.acclorite.book_story.presentation.screens.reader.components.ReaderBottomBar import ua.acclorite.book_story.presentation.screens.reader.components.ReaderEndItem -import ua.acclorite.book_story.presentation.screens.reader.components.ReaderSettingsBottomSheet import ua.acclorite.book_story.presentation.screens.reader.components.ReaderStartItem -import ua.acclorite.book_story.presentation.screens.reader.components.ReaderTopBar +import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderBottomBar +import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderTopBar +import ua.acclorite.book_story.presentation.screens.reader.components.settings_bottom_sheet.ReaderSettingsBottomSheet import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel import ua.acclorite.book_story.ui.elevation @@ -78,9 +79,9 @@ import ua.acclorite.book_story.util.Constants @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable fun ReaderScreen( - mainViewModel: MainViewModel = hiltViewModel(), - libraryViewModel: LibraryViewModel = hiltViewModel(), - historyViewModel: HistoryViewModel = hiltViewModel(), + mainViewModel: MainViewModel, + libraryViewModel: LibraryViewModel, + historyViewModel: HistoryViewModel, navigator: Navigator ) { val context = LocalContext.current as ComponentActivity @@ -189,7 +190,7 @@ fun ReaderScreen( Modifier .fillMaxSize() .nestedScroll(nestedScrollConnection), - containerColor = Color(backgroundColor.toULong()), + containerColor = MaterialTheme.colorScheme.surface, topBar = { AnimatedVisibility( visible = state.showMenu, @@ -219,19 +220,66 @@ fun ReaderScreen( } } ) { - SelectionContainer { + CustomSelectionContainer( + onCopyRequested = { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + Toast.makeText( + context, + context.getString(R.string.copied), + Toast.LENGTH_SHORT + ).show() + } + }, + onTranslateRequested = { textToTranslate -> + viewModel.onEvent( + ReaderEvent.OnTranslateText( + textToTranslate, + context, + noAppsFound = { + Toast.makeText( + context, + context.getString(R.string.error_no_translator), + Toast.LENGTH_SHORT + ).show() + } + ) + ) + }, + onDictionaryRequested = { textToDefine -> + viewModel.onEvent( + ReaderEvent.OnOpenDictionary( + textToDefine, + context, + noAppsFound = { + Toast.makeText( + context, + context.getString(R.string.error_no_browser), + Toast.LENGTH_SHORT + ).show() + } + ) + ) + } + ) { toolbarShowed -> LazyColumn( state = listState, modifier = Modifier - .fillMaxSize() - .clickable( - interactionSource = null, - indication = null, - enabled = !loading, - onClick = { - viewModel.onEvent( - ReaderEvent.OnShowHideMenu(context = context) - ) + .then( + if (!loading && toolbarShowed) { + Modifier + .fillMaxSize() + .clickable( + interactionSource = null, + indication = null, + onClick = { + viewModel.onEvent( + ReaderEvent.OnShowHideMenu(context = context) + ) + } + ) + } else { + Modifier + .fillMaxSize() } ) ) { @@ -246,28 +294,31 @@ fun ReaderScreen( itemsIndexed( state.book.text, key = { _, key -> key.id } ) { index, text -> - if (index == 0) - Spacer(modifier = Modifier.height(36.dp)) + Column( + Modifier + .background(Color(backgroundColor.toULong())) + .fillMaxWidth() + .padding( + top = if (index == 0) 36.dp else 0.dp, + start = 18.dp, + end = 18.dp, + bottom = if (index == state.book.text.lastIndex) 36.dp + else (paragraphHeight * 3).dp + ) + ) { + Text( + text = "${if (paragraphIndentation) " " else ""}${text.line}", + color = Color(fontColor.toULong()), - 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)) + style = TextStyle( + lineBreak = LineBreak.Paragraph + ), + fontFamily = fontFamily.font, + fontStyle = fontStyle, + fontSize = fontSize.sp, + lineHeight = (fontSize + lineHeight).sp + ) + } } if (state.book.text.isNotEmpty()) { @@ -300,6 +351,9 @@ fun ReaderScreen( actionTitle = stringResource(id = R.string.go_back), action = { navigator.navigateBack() + libraryViewModel.onEvent(LibraryEvent.OnLoadList) + historyViewModel.onEvent(HistoryEvent.OnLoadList) + viewModel.onEvent(ReaderEvent.OnShowSystemBars(context)) } ) } else { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderEndItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderEndItem.kt index 47d27f7d..cc1b5c15 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderEndItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderEndItem.kt @@ -4,11 +4,15 @@ import android.widget.Toast import androidx.activity.ComponentActivity import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.PaddingValues 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.foundation.layout.navigationBarsIgnoringVisibility import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -26,13 +30,17 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.Category -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.Screen +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel +/** + * Reader end item. Displays at the end of the book. + */ +@OptIn(ExperimentalLayoutApi::class) @Composable fun ReaderEndItem( libraryViewModel: LibraryViewModel, @@ -46,7 +54,13 @@ fun ReaderEndItem( modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.surface) - .padding(24.dp), + .windowInsetsPadding(WindowInsets.navigationBarsIgnoringVisibility) + .padding( + top = 24.dp, + start = 24.dp, + end = 24.dp, + bottom = 8.dp + ), horizontalAlignment = Alignment.CenterHorizontally ) { Text( @@ -56,7 +70,14 @@ fun ReaderEndItem( textAlign = TextAlign.Center, overflow = TextOverflow.Ellipsis ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(4.dp)) + Text( + stringResource(id = R.string.letters_and_words, state.letters, state.words), + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(12.dp)) TextButton( onClick = { if (state.book.category != Category.ALREADY_READ) { @@ -86,7 +107,7 @@ fun ReaderEndItem( context ) ) - navigator.navigate(Screen.LIBRARY) + navigator.navigate(Screen.LIBRARY, true) } }, contentPadding = PaddingValues(horizontal = 12.dp), diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderStartItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderStartItem.kt index b397dafa..d209e611 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderStartItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderStartItem.kt @@ -5,14 +5,18 @@ 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.ExperimentalLayoutApi import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets 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.statusBarsIgnoringVisibility import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Image import androidx.compose.material3.Icon @@ -24,7 +28,6 @@ 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 @@ -33,6 +36,10 @@ import ua.acclorite.book_story.R import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel import ua.acclorite.book_story.ui.elevation +/** + * Reader start item. Displays at the beginning of the book. + */ +@OptIn(ExperimentalLayoutApi::class) @Composable fun ReaderStartItem(viewModel: ReaderViewModel) { val state by viewModel.state.collectAsState() @@ -41,6 +48,7 @@ fun ReaderStartItem(viewModel: ReaderViewModel) { modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.surface) + .windowInsetsPadding(WindowInsets.statusBarsIgnoringVisibility) .padding(24.dp) ) { Row( @@ -66,7 +74,7 @@ fun ReaderStartItem(viewModel: ReaderViewModel) { if (state.book.coverImage != null) { Image( - bitmap = state.book.coverImage!!.asImageBitmap(), + bitmap = state.book.coverImage!!, contentDescription = "Cover", modifier = Modifier .fillMaxSize() diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderBottomBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderBottomBar.kt similarity index 96% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderBottomBar.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderBottomBar.kt index 6ae3075a..ea21c461 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderBottomBar.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderBottomBar.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.reader.components +package ua.acclorite.book_story.presentation.screens.reader.components.app_bar import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -19,7 +19,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import ua.acclorite.book_story.presentation.Navigator +import ua.acclorite.book_story.presentation.data.Navigator import ua.acclorite.book_story.presentation.data.removeDigits import ua.acclorite.book_story.presentation.data.removeTrailingZero import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent @@ -27,6 +27,9 @@ import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewMode import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel +/** + * Reader bottom bar. Has a slider to change progress. + */ @Composable fun ReaderBottomBar( viewModel: ReaderViewModel, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTopBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderTopBar.kt similarity index 82% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTopBar.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderTopBar.kt index 37a88904..678b341a 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTopBar.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderTopBar.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.reader.components +package ua.acclorite.book_story.presentation.screens.reader.components.app_bar import androidx.activity.ComponentActivity import androidx.compose.foundation.basicMarquee @@ -6,10 +6,7 @@ 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 @@ -28,17 +25,20 @@ 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 ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.Argument -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.Screen +import ua.acclorite.book_story.presentation.components.GoBackButton +import ua.acclorite.book_story.presentation.data.Argument +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.presentation.data.removeDigits import ua.acclorite.book_story.presentation.data.removeTrailingZero import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel +/** + * Reader top bar. Displays title of the book. + */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun ReaderTopBar(viewModel: ReaderViewModel, navigator: Navigator, containerColor: Color) { @@ -48,15 +48,8 @@ fun ReaderTopBar(viewModel: ReaderViewModel, navigator: Navigator, containerColo TopAppBar( navigationIcon = { - IconButton(onClick = { - navigator.navigateBack() + GoBackButton(navigator = navigator) { viewModel.onEvent(ReaderEvent.OnShowSystemBars(context)) - }) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Go back", - tint = MaterialTheme.colorScheme.onSurface - ) } }, title = { @@ -75,6 +68,7 @@ fun ReaderTopBar(viewModel: ReaderViewModel, navigator: Navigator, containerColo onClick = { navigator.navigateWithoutBackStack( Screen.BOOK_INFO, + true, Argument( "book", book @@ -86,7 +80,6 @@ fun ReaderTopBar(viewModel: ReaderViewModel, navigator: Navigator, containerColo delayMillis = 4000 ) ) - Spacer(modifier = Modifier.height(4.dp)) Text( stringResource( id = R.string.read_query, @@ -107,8 +100,7 @@ fun ReaderTopBar(viewModel: ReaderViewModel, navigator: Navigator, containerColo }) { Icon( imageVector = Icons.Default.Settings, - contentDescription = "Open Settings", - tint = MaterialTheme.colorScheme.onSurface + contentDescription = stringResource(id = R.string.open_reader_settings_content_desc), ) } }, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderSettingsBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheet.kt similarity index 98% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderSettingsBottomSheet.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheet.kt index 0e1c507b..fe09675c 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderSettingsBottomSheet.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheet.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.reader.components +package ua.acclorite.book_story.presentation.screens.reader.components.settings_bottom_sheet import androidx.activity.ComponentActivity import androidx.compose.animation.animateColorAsState @@ -48,6 +48,9 @@ import ua.acclorite.book_story.ui.ElevationDefaults import ua.acclorite.book_story.ui.elevation import ua.acclorite.book_story.util.Constants +/** + * Settings bottom sheet. Has General and Colors categories. + */ @OptIn( ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class ) @@ -118,7 +121,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie } item { CategoryTitle( - title = stringResource(id = R.string.font_settings), + title = stringResource(id = R.string.font_reader_settings), color = MaterialTheme.colorScheme.onSurface ) } @@ -197,7 +200,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie } item { CategoryTitle( - title = stringResource(id = R.string.text_settings), + title = stringResource(id = R.string.text_reader_settings), color = MaterialTheme.colorScheme.onSurface ) } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderSettingsBottomSheetTabRow.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheetTabRow.kt similarity index 96% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderSettingsBottomSheetTabRow.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheetTabRow.kt index 28d787c4..71818fc6 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderSettingsBottomSheetTabRow.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheetTabRow.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.reader.components +package ua.acclorite.book_story.presentation.screens.reader.components.settings_bottom_sheet import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.fillMaxWidth @@ -21,6 +21,9 @@ import ua.acclorite.book_story.R import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel +/** + * Settings bottom sheet tab row. It is used to switch between categories. + */ @OptIn(ExperimentalFoundationApi::class) @Composable fun ReaderSettingsBottomSheetTabRow(viewModel: ReaderViewModel, pagerState: PagerState) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt index f9294234..92bcc2e9 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt @@ -7,10 +7,10 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.pager.PagerState import ua.acclorite.book_story.domain.model.Book -import ua.acclorite.book_story.presentation.Navigator +import ua.acclorite.book_story.presentation.data.Navigator sealed class ReaderEvent { - data class OnFileNotFound(val onLoaded: () -> Unit) : ReaderEvent() + data class OnTextIsEmpty(val onLoaded: () -> Unit) : ReaderEvent() data class OnLoadText( val scrollState: LazyListState, val navigator: Navigator, @@ -37,4 +37,16 @@ sealed class ReaderEvent { val updatePage: (Int) -> Unit, val navigator: Navigator ) : ReaderEvent() + + data class OnTranslateText( + val textToTranslate: String, + val context: ComponentActivity, + val noAppsFound: () -> Unit + ) : ReaderEvent() + + data class OnOpenDictionary( + val textToDefine: String, + val context: ComponentActivity, + val noAppsFound: () -> Unit + ) : ReaderEvent() } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt index 056f6758..5bca3077 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt @@ -7,6 +7,9 @@ import ua.acclorite.book_story.util.UIText @Immutable data class ReaderState( val book: Book, + val letters: Int = 0, + val words: Int = 0, + val errorMessage: UIText? = null, val showMenu: Boolean = false, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt index 1184f683..c6d59ae0 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt @@ -1,5 +1,7 @@ package ua.acclorite.book_story.presentation.screens.reader.data +import android.app.SearchManager +import android.content.Intent import androidx.activity.ComponentActivity import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.lazy.LazyListState @@ -24,13 +26,11 @@ import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.Category import ua.acclorite.book_story.domain.model.History -import ua.acclorite.book_story.domain.use_case.GetText import ua.acclorite.book_story.domain.use_case.InsertHistory import ua.acclorite.book_story.domain.use_case.UpdateBooks -import ua.acclorite.book_story.presentation.Argument -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.Screen -import ua.acclorite.book_story.util.Resource +import ua.acclorite.book_story.presentation.data.Argument +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.util.UIText import java.util.Date import kotlin.math.roundToInt @@ -39,7 +39,6 @@ import kotlin.math.roundToInt @HiltViewModel(assistedFactory = ReaderViewModel.Factory::class) class ReaderViewModel @AssistedInject constructor( @Assisted book: Book, - private val getText: GetText, private val updateBooks: UpdateBooks, private val insertHistory: InsertHistory ) : ViewModel() { @@ -49,61 +48,29 @@ class ReaderViewModel @AssistedInject constructor( fun onEvent(event: ReaderEvent) { when (event) { - is ReaderEvent.OnFileNotFound -> { + is ReaderEvent.OnTextIsEmpty -> { _state.update { event.onLoaded() it.copy( - errorMessage = UIText.StringResource(R.string.error_file_not_found), + errorMessage = UIText.StringResource(R.string.error_no_text), ) } } 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 - } - } - } - val time = Date().time + val text = _state.value.book.text.joinToString(separator = " ") { it.line } + val letters = text.replace(" ", "").length + val words = text.split("\\s+".toRegex()).size _state.update { it.copy( book = it.book.copy( lastOpened = time - ) + ), + letters = letters, + words = words ) } @@ -114,7 +81,7 @@ class ReaderViewModel @AssistedInject constructor( listOf( History( null, - _state.value.book, + _state.value.book.id!!, time ) ) @@ -141,7 +108,7 @@ class ReaderViewModel @AssistedInject constructor( } } - delay(500) + delay(100) event.onLoaded() return@collectLatest } @@ -234,7 +201,8 @@ class ReaderViewModel @AssistedInject constructor( onEvent(ReaderEvent.OnShowSystemBars(event.context)) val book = _state.value.book.copy( - category = Category.ALREADY_READ + category = Category.ALREADY_READ, + progress = 1f ) updateBooks.execute(listOf(book)) @@ -245,7 +213,59 @@ class ReaderViewModel @AssistedInject constructor( it != Category.ALREADY_READ }.size - 1 ) - event.navigator.navigate(Screen.LIBRARY) + event.navigator.navigate(Screen.LIBRARY, true) + } + } + + is ReaderEvent.OnTranslateText -> { + viewModelScope.launch { + val translatorIntent = Intent() + val browserIntent = Intent() + + translatorIntent.type = "text/plain" + translatorIntent.action = Intent.ACTION_PROCESS_TEXT + + browserIntent.action = Intent.ACTION_WEB_SEARCH + + translatorIntent.putExtra(Intent.EXTRA_PROCESS_TEXT, event.textToTranslate) + translatorIntent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, true) + + browserIntent.putExtra( + SearchManager.QUERY, + "translate: ${event.textToTranslate.trim()}" + ) + + if (translatorIntent.resolveActivity(event.context.packageManager) != null) { + event.context.startActivity(translatorIntent) + return@launch + } + + if (browserIntent.resolveActivity(event.context.packageManager) != null) { + event.context.startActivity(browserIntent) + return@launch + } + + event.noAppsFound() + } + } + + is ReaderEvent.OnOpenDictionary -> { + viewModelScope.launch { + val browserIntent = Intent() + + browserIntent.action = Intent.ACTION_WEB_SEARCH + browserIntent.putExtra( + SearchManager.QUERY, + "dictionary" + + ": ${event.textToDefine.trim()}" + ) + + if (browserIntent.resolveActivity(event.context.packageManager) != null) { + event.context.startActivity(browserIntent) + return@launch + } + + event.noAppsFound() } } } @@ -270,8 +290,8 @@ class ReaderViewModel @AssistedInject constructor( ReaderState(book = book) } - if (book.file == null) { - onEvent(ReaderEvent.OnFileNotFound(onLoaded = { onLoaded() })) + if (book.text.isEmpty()) { + onEvent(ReaderEvent.OnTextIsEmpty(onLoaded = { onLoaded() })) } else { onEvent(ReaderEvent.OnShowHideMenu(false, context)) onEvent( @@ -304,3 +324,5 @@ class ReaderViewModel @AssistedInject constructor( + + diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/SettingsScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/SettingsScreen.kt index 77985539..c2f2e0bd 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/SettingsScreen.kt @@ -6,13 +6,13 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.MenuBook import androidx.compose.material.icons.filled.DisplaySettings import androidx.compose.material.icons.filled.Palette import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -21,19 +21,24 @@ import androidx.compose.runtime.Composable 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 ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.Screen -import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar +import ua.acclorite.book_story.presentation.components.GoBackButton +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.presentation.screens.settings.components.SettingsCategoryItem +import ua.acclorite.book_story.ui.elevation @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( navigator: Navigator ) { - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val listState = rememberLazyListState() + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( + canScroll = { + listState.canScrollForward + } + ) Scaffold( Modifier @@ -42,36 +47,26 @@ fun SettingsScreen( .windowInsetsPadding(WindowInsets.navigationBars), containerColor = MaterialTheme.colorScheme.surface, topBar = { - AnimatedTopAppBar( + LargeTopAppBar( + title = { + Text(stringResource(id = R.string.settings_screen)) + }, + navigationIcon = { + GoBackButton(navigator = navigator) + }, scrollBehavior = scrollBehavior, - isTopBarScrolled = null, - - content1NavigationIcon = { - IconButton(onClick = { navigator.navigateBack() }) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Go back", - tint = MaterialTheme.colorScheme.onSurface - ) - } - }, - content1Title = { - Text( - stringResource(id = R.string.settings_screen), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface, - overflow = TextOverflow.Ellipsis, - maxLines = 1 - ) - }, - content1Actions = {} + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + scrolledContainerColor = MaterialTheme.elevation() + ) ) } ) { paddingValues -> LazyColumn( Modifier .fillMaxSize() - .padding(top = paddingValues.calculateTopPadding()) + .padding(top = paddingValues.calculateTopPadding()), + state = listState ) { item { SettingsCategoryItem( @@ -79,7 +74,7 @@ fun SettingsScreen( text = stringResource(id = R.string.general_settings), description = stringResource(id = R.string.general_settings_desc) ) { - navigator.navigate(Screen.GENERAL_SETTINGS) + navigator.navigate(Screen.GENERAL_SETTINGS, false) } } @@ -89,11 +84,19 @@ fun SettingsScreen( text = stringResource(id = R.string.appearance_settings), description = stringResource(id = R.string.appearance_settings_desc) ) { - navigator.navigate(Screen.APPEARANCE_SETTINGS) + navigator.navigate(Screen.APPEARANCE_SETTINGS, false) } } - //todo Reader settings + item { + SettingsCategoryItem( + icon = Icons.AutoMirrored.Filled.MenuBook, + text = stringResource(id = R.string.reader_settings), + description = stringResource(id = R.string.reader_settings_desc) + ) { + navigator.navigate(Screen.READER_SETTINGS, false) + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/CheckboxWIthTitle.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/CheckboxWIthTitle.kt index e7fbcf54..afa52624 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/CheckboxWIthTitle.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/CheckboxWIthTitle.kt @@ -14,6 +14,9 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import ua.acclorite.book_story.presentation.components.CategoryTitle +/** + * Checkbox with title. + */ @Composable fun CheckboxWithTitle( selected: Boolean, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/ChipsWithTitle.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/ChipsWithTitle.kt index 8d8fa4d0..76516e72 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/ChipsWithTitle.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/ChipsWithTitle.kt @@ -22,6 +22,9 @@ import androidx.compose.ui.unit.dp import ua.acclorite.book_story.domain.model.ChipItem import ua.acclorite.book_story.presentation.components.CategoryTitle +/** + * Chips with title. Use list of [ChipItem]s to display chips. + */ @OptIn(ExperimentalMaterialApi::class, ExperimentalLayoutApi::class) @Composable fun ChipsWithTitle( diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/ColorPickerWithTitle.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/ColorPickerWithTitle.kt index 3396ee86..2906f581 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/ColorPickerWithTitle.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/ColorPickerWithTitle.kt @@ -28,6 +28,9 @@ import androidx.compose.ui.unit.dp import ua.acclorite.book_story.R import ua.acclorite.book_story.presentation.components.CategoryTitle +/** + * Color picker with title. + */ @Composable fun ColorPickerWithTitle( value: Color, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/SettingsCategoryItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/SettingsCategoryItem.kt index 3babc5c4..6742a3d1 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/SettingsCategoryItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/SettingsCategoryItem.kt @@ -17,10 +17,14 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +/** + * Settings category item. It is used to represent the whole category. + */ @Composable fun SettingsCategoryItem( icon: ImageVector, @@ -53,13 +57,17 @@ fun SettingsCategoryItem( text, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurface, - fontSize = 18.sp + fontSize = 18.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis ) Spacer(modifier = Modifier.height(2.dp)) Text( description, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis ) } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/SliderWithTitle.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/SliderWithTitle.kt index c3c048ec..63515b35 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/SliderWithTitle.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/SliderWithTitle.kt @@ -18,6 +18,9 @@ import androidx.compose.ui.unit.dp import ua.acclorite.book_story.presentation.components.CategoryTitle import kotlin.math.roundToInt +/** + * Slider with title. + */ @Composable fun SliderWithTitle( modifier: Modifier = Modifier, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/AppearanceSettings.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/AppearanceSettings.kt index 5405f01a..cff4c1bb 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/AppearanceSettings.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/AppearanceSettings.kt @@ -1,16 +1,16 @@ package ua.acclorite.book_story.presentation.screens.settings.nested.appearance +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -18,29 +18,39 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color 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 androidx.compose.ui.unit.dp import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.ChipItem -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar +import ua.acclorite.book_story.presentation.components.CategoryTitle +import ua.acclorite.book_story.presentation.components.GoBackButton import ua.acclorite.book_story.presentation.data.MainEvent import ua.acclorite.book_story.presentation.data.MainViewModel +import ua.acclorite.book_story.presentation.data.Navigator import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle -import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.AppearanceSettingsThemeSwitcher +import ua.acclorite.book_story.presentation.screens.settings.components.ColorPickerWithTitle +import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher.AppearanceSettingsThemeSwitcher import ua.acclorite.book_story.ui.DarkTheme +import ua.acclorite.book_story.ui.elevation @OptIn(ExperimentalMaterial3Api::class) @Composable fun AppearanceSettings( - mainViewModel: MainViewModel = hiltViewModel(), + mainViewModel: MainViewModel, navigator: Navigator ) { - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val listState = rememberLazyListState() + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( + canScroll = { + listState.canScrollForward + } + ) val darkTheme = mainViewModel.darkTheme.collectAsState().value!! + val backgroundColor = mainViewModel.backgroundColor.collectAsState().value!! + val fontColor = mainViewModel.fontColor.collectAsState().value!! Scaffold( Modifier @@ -49,37 +59,40 @@ fun AppearanceSettings( .windowInsetsPadding(WindowInsets.navigationBars), containerColor = MaterialTheme.colorScheme.surface, topBar = { - AnimatedTopAppBar( + LargeTopAppBar( + title = { + Text(stringResource(id = R.string.appearance_settings)) + }, + navigationIcon = { + GoBackButton(navigator = navigator) + }, scrollBehavior = scrollBehavior, - isTopBarScrolled = null, - - content1NavigationIcon = { - IconButton(onClick = { navigator.navigateBack() }) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Go back", - tint = MaterialTheme.colorScheme.onSurface - ) - } - }, - content1Title = { - Text( - stringResource(id = R.string.appearance_settings), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface, - overflow = TextOverflow.Ellipsis, - maxLines = 1 - ) - }, - content1Actions = {} + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + scrolledContainerColor = MaterialTheme.elevation() + ) ) } ) { paddingValues -> LazyColumn( Modifier .fillMaxSize() - .padding(top = paddingValues.calculateTopPadding()) + .padding(top = paddingValues.calculateTopPadding()), + state = listState ) { + item { + Spacer(modifier = Modifier.height(8.dp)) + } + item { + CategoryTitle( + title = stringResource(id = R.string.theme_appearance_settings), + color = MaterialTheme.colorScheme.primary + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + } + item { ChipsWithTitle( title = stringResource(id = R.string.dark_theme_option), @@ -107,6 +120,46 @@ fun AppearanceSettings( item { AppearanceSettingsThemeSwitcher(mainViewModel = mainViewModel) } + + item { + Spacer(modifier = Modifier.height(22.dp)) + } + item { + CategoryTitle( + title = stringResource(id = R.string.colors_appearance_settings), + color = MaterialTheme.colorScheme.primary + ) + } + item { + Spacer(modifier = Modifier.height(8.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() + ) + ) + } + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcher.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcher.kt similarity index 97% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcher.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcher.kt index d543800d..0a73e1b6 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcher.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcher.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components +package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher import android.os.Build import androidx.compose.foundation.layout.Column @@ -21,6 +21,9 @@ import ua.acclorite.book_story.ui.Theme import ua.acclorite.book_story.ui.isDark import ua.acclorite.book_story.util.Constants +/** + * Theme switcher. + */ @Composable fun AppearanceSettingsThemeSwitcher( mainViewModel: MainViewModel diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcherItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcherItem.kt similarity index 98% rename from app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcherItem.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcherItem.kt index df5e3b9b..e4bdd814 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/AppearanceSettingsThemeSwitcherItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcherItem.kt @@ -1,4 +1,4 @@ -package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components +package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -33,6 +33,9 @@ import ua.acclorite.book_story.ui.Theme import ua.acclorite.book_story.ui.colorScheme import ua.acclorite.book_story.util.UIText +/** + * Theme switcher item. + */ @Composable fun AppearanceSettingsThemeSwitcherItem( theme: Pair, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/general/GeneralSettings.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/general/GeneralSettings.kt index 01cc5d76..2d2d8160 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/general/GeneralSettings.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/general/GeneralSettings.kt @@ -7,11 +7,9 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -22,24 +20,28 @@ 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 ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.ChipItem -import ua.acclorite.book_story.presentation.Navigator -import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar +import ua.acclorite.book_story.presentation.components.GoBackButton import ua.acclorite.book_story.presentation.data.MainEvent import ua.acclorite.book_story.presentation.data.MainViewModel +import ua.acclorite.book_story.presentation.data.Navigator import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle +import ua.acclorite.book_story.ui.elevation import ua.acclorite.book_story.util.Constants @OptIn(ExperimentalMaterial3Api::class) @Composable fun GeneralSettings( - mainViewModel: MainViewModel = hiltViewModel(), + mainViewModel: MainViewModel, navigator: Navigator ) { - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val listState = rememberLazyListState() + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( + canScroll = { + listState.canScrollForward + } + ) val context = LocalContext.current as ComponentActivity val language = mainViewModel.language.collectAsState().value!! @@ -51,36 +53,26 @@ fun GeneralSettings( .windowInsetsPadding(WindowInsets.navigationBars), containerColor = MaterialTheme.colorScheme.surface, topBar = { - AnimatedTopAppBar( + LargeTopAppBar( + title = { + Text(stringResource(id = R.string.general_settings)) + }, + navigationIcon = { + GoBackButton(navigator = navigator) + }, scrollBehavior = scrollBehavior, - isTopBarScrolled = null, - - content1NavigationIcon = { - IconButton(onClick = { navigator.navigateBack() }) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Go back", - tint = MaterialTheme.colorScheme.onSurface - ) - } - }, - content1Title = { - Text( - stringResource(id = R.string.general_settings), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface, - overflow = TextOverflow.Ellipsis, - maxLines = 1 - ) - }, - content1Actions = {} + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + scrolledContainerColor = MaterialTheme.elevation() + ) ) } ) { paddingValues -> LazyColumn( Modifier .fillMaxSize() - .padding(top = paddingValues.calculateTopPadding()) + .padding(top = paddingValues.calculateTopPadding()), + state = listState ) { item { ChipsWithTitle( diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt new file mode 100644 index 00000000..a1b10ba3 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt @@ -0,0 +1,217 @@ +package ua.acclorite.book_story.presentation.screens.settings.nested.reader + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.unit.dp +import ua.acclorite.book_story.R +import ua.acclorite.book_story.domain.model.ChipItem +import ua.acclorite.book_story.presentation.components.CategoryTitle +import ua.acclorite.book_story.presentation.components.GoBackButton +import ua.acclorite.book_story.presentation.data.MainEvent +import ua.acclorite.book_story.presentation.data.MainViewModel +import ua.acclorite.book_story.presentation.data.Navigator +import ua.acclorite.book_story.presentation.screens.settings.components.CheckboxWithTitle +import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle +import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle +import ua.acclorite.book_story.ui.elevation +import ua.acclorite.book_story.util.Constants + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ReaderSettings( + mainViewModel: MainViewModel, + navigator: Navigator +) { + val listState = rememberLazyListState() + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( + canScroll = { + listState.canScrollForward + } + ) + + 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!! + + Scaffold( + Modifier + .fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection) + .windowInsetsPadding(WindowInsets.navigationBars), + containerColor = MaterialTheme.colorScheme.surface, + topBar = { + LargeTopAppBar( + title = { + Text(stringResource(id = R.string.reader_settings)) + }, + navigationIcon = { + GoBackButton(navigator = navigator) + }, + scrollBehavior = scrollBehavior, + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + scrolledContainerColor = MaterialTheme.elevation() + ) + ) + } + ) { paddingValues -> + LazyColumn( + Modifier + .fillMaxSize() + .padding(top = paddingValues.calculateTopPadding()), + state = listState + ) { + item { + Spacer(modifier = Modifier.height(8.dp)) + } + item { + CategoryTitle( + title = stringResource(id = R.string.font_reader_settings), + color = MaterialTheme.colorScheme.primary + ) + } + 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_reader_settings), + color = MaterialTheme.colorScheme.primary + ) + } + 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) + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/Transitions.kt b/app/src/main/java/ua/acclorite/book_story/ui/Transitions.kt index 71e701e7..71b8279f 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/Transitions.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/Transitions.kt @@ -17,10 +17,16 @@ object Transitions { val FadeTransitionOut = fadeOut(tween(50)) val SlidingTransitionIn = fadeIn(tween(300)) + - slideInHorizontally(tween(300)) { it / 15 } + slideInHorizontally(tween(300)) { it / 16 } + + val BackSlidingTransitionIn = fadeIn(tween(300)) + + slideInHorizontally(tween(300)) { -it / 16 } val SlidingTransitionOut = fadeOut(tween(200)) + - slideOutHorizontally(tween(200)) { it / 25 } + slideOutHorizontally(tween(300)) { -it / 16 } + + val BackSlidingTransitionOut = fadeOut(tween(200)) + + slideOutHorizontally(tween(300)) { it / 16 } } @Composable diff --git a/app/src/main/java/ua/acclorite/book_story/util/UIText.kt b/app/src/main/java/ua/acclorite/book_story/util/UIText.kt index c859b54d..223ccf24 100644 --- a/app/src/main/java/ua/acclorite/book_story/util/UIText.kt +++ b/app/src/main/java/ua/acclorite/book_story/util/UIText.kt @@ -6,9 +6,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource -sealed class UIText { - data class StringValue(val value: String) : UIText() - class StringResource(@StringRes val resId: Int, vararg val args: Any) : UIText() +sealed class UIText(val string: String?) { + data class StringValue(val value: String) : UIText(value) + class StringResource(@StringRes val resId: Int, vararg val args: Any) : UIText(null) @Composable fun asString(): String { diff --git a/app/src/main/res/drawable/app_icon_colored.png b/app/src/main/res/drawable/app_icon_colored.png index e76100d1e27412a9b0093ab50ae5a2458fd39acf..3615361af6de979e8547b96fe7228e6f97e6d84c 100644 GIT binary patch literal 29654 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 W!OwNF)$ke!{L|LZSFcpHjrd literal 14963 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 diff --git a/app/src/main/res/drawable/app_icon_monochrome.png b/app/src/main/res/drawable/app_icon_monochrome.png new file mode 100644 index 0000000000000000000000000000000000000000..0bc46a280f339ed26136d5c3857f1eda9cb735ed GIT binary patch literal 5996 zcmeI0iC2@?w!jYo1w{s>iV}uetK3^5*fIpgfMRjDRH#Y@3V~26hyemZ2=kytsu0lH zf(l60D)SnYcTblObpG?lL*yC1 zn!#h$SSD`Y`q!we{RV3hI(e63yggl~Y^I--Y&L%4E|Iln@a6t2!L#e(Wmig6sf&u` zq(O>m@GGs9|7X+0rLnLn1Wr08O@RH;OU=^|KT@cEcoVNzJ=#0E_Xe@FCTaOF6)UF6 zsotM8gdsZYn~4f%I21bMx5#X(<*KYTs&EFGL53JeQ3W22xDTV3n|Cq_@HSQMjY-QR zX-Zd6cqWXxpDO6#{J}jz^$|ZB*j*ee>`~7)7H6aknzwg{a&IO3Duhxam<-XdXjy+( zO`Kk)LxPWD242J!B{)T(UP3&yq6;lP2kW~O*i@>|%!>-_u%GX$Isq9nQgF2PVGhX= z73uWhPHIc?5d|4$Kn`|a@*6&;D)3fDdKqn7P>Ghxlvp7I)<8i!9H+ZXSC698PRlx(=5vAAEQKY2haC$D&~ zy64tL9W+&v@1jH&x0$=9(1;2%*cbic&Wme^j$xRE&&ZhaOn5j6PeX!eYf)3=?p?p< zLX-;B@ZNN4(10vQ1l<_qWT!}aHh|gVt@W2L?%u53_UiSkgZd5hX4|u`(298qWtWqM zI@E&kZue#uWo&KRJZXv>lPN3T{1BoarpVw4&BSR)O!aGOjZ0uZp*sQj;7w;URDtp8IS(H?J_c*d< z+o;3^rnua_NuRM0;4}O%<;ic0l!*KJDFg)zNq?g()HFG@CANI@PFD<_=0`43tV1c{ zi&8vf%U+DzM2dpbj(2%AOf5TQdFg|| z+x&hFhBbNORxC%Xlupv|!A#bf=e?CT1_)N^QQa9DWR6=pWL4|!2qb*GByNOtp zk2wD<-X)zB;<5ME`ftkm5-0g3OlY8IS4Q{CkULQ{B?a2$hW;PVV)Avz$ZEXl*Q zZS8qg^WpXydPt<`qo;U7=ao8(;sDxdagfS9{Oht$KxjIufkKXM`We>?!Nb;B3?2t zAH?lhQ0$)j}h;sxG!BrH(NKHX6~n{S7ueNfBCTG&ew+3r<+Z|?AQ=$5W^|S zBO__1D_y*ocCs5eSWz&>o}G8*Ouo}XslUSd%jV}!Hlxw0mUh$6c-yl)nCf7KiG|^P z0=plQGu72|(9BeXx}@a<2Z|*VW0Wa`&mc-K z6V6yalq_)xLkI&@66uXx+=H4ihO+6PSc9fF?2oyG)4Yl^M|7<6Ht%rih0-fxCk9wJ z6aPj_i>`D{ffNIqyivO6gezLZwY{tgQJGUEI7~w_S^QA|i;U|%O-{=O7Yli--525} zs;krk!$gkJyAf&Lom|psv|sz^)#UW~`wcR#zH_fC@pSVX;=F>}$ero)D#;@n0JI$u zL>eIUC9kBz5i5~J*v02q^Y*QvIx62!(_CYb7H(|p|M(G|FQ(m@t4qU;AHWk=&V!D| zJ$-~f7G%I++bg_|TgZ6g%SM zLQcK%8V{qY-tq6zRY5omBguTNdxdgXy2jfaVS5V33_w2+dd)0~*I?S4T;p&0W$yj(4k~F-JI*9h6$j-g%b;=m}is9pc34xhPGK;y*bn@sr=1<;HJ~d8|FW9?+o- zB&g=^>^edlvve`@mZF5Qkicfxqxp4u3za-2;C>IaT7;Xag7u>}yyo7q-|v#2BU zIoJ(=)Y=$A9*?Tfan(V9O2LC7)E_f{Rdfqce{eN@tAOo6kg3?Cz5OHk7MW(%(v5k@ zSp*4(u222Nqc3{JI?Jp^Dp%dfNCLLe$n>Si14bQdOaUNyYW=LsoFWVYe&PJeH~|L_)ca7$|@;>wyP;&g3~fJ~}ec&Egj)+t%-^Q%^s z5?HIsmW77y=$K#i0D+xj|8dNO_^v7I%9o∋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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/splash_icon.xml b/app/src/main/res/drawable/splash_icon.xml index a088bfa2..fd0864f8 100644 --- a/app/src/main/res/drawable/splash_icon.xml +++ b/app/src/main/res/drawable/splash_icon.xml @@ -2,7 +2,7 @@ \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/app_icon.xml b/app/src/main/res/mipmap-anydpi-v26/app_icon.xml similarity index 60% rename from app/src/main/res/mipmap-anydpi/app_icon.xml rename to app/src/main/res/mipmap-anydpi-v26/app_icon.xml index be2d22d2..e32ce33f 100644 --- a/app/src/main/res/mipmap-anydpi/app_icon.xml +++ b/app/src/main/res/mipmap-anydpi-v26/app_icon.xml @@ -1,6 +1,6 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/app_icon_round.xml b/app/src/main/res/mipmap-anydpi-v26/app_icon_round.xml similarity index 60% rename from app/src/main/res/mipmap-anydpi/app_icon_round.xml rename to app/src/main/res/mipmap-anydpi-v26/app_icon_round.xml index be2d22d2..e32ce33f 100644 --- a/app/src/main/res/mipmap-anydpi/app_icon_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/app_icon_round.xml @@ -1,6 +1,6 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/app_icon.webp b/app/src/main/res/mipmap-hdpi/app_icon.webp index 62ba9e1cb69f66b9f184826e3e5c696be02edf95..98437d1891bd4a1d7f0e9f56e40663270b8b50f1 100644 GIT binary patch literal 1546 zcmV+l2KD(;Nk&Ej1^@t8MM6+kP&iEX1pojqN5ByfwT6PWjhvc4>>EE2M8pJSG*$ex zgamH2ZOfB)q6Dev(qcO?LY6=WNQYGJ?$Elseev(hc^|BN<8GBpgwxu#6=yKaOg4%; zAej{z9iJA)MlmR6P&2843cz;gC~n&}qVnnh=LnKuYdapvw*ENz^0#fY-8mf z+qP}nwy`so$zOj`^L5p%ZF|0z1KGCIRN0T$9Sa(YG{xQB3S7yyO+qP{p zGo$wbw(Zc)Y}@vGm;GntHj*M|mTnAhkpFj1(WyX+2EZu^sD?26;jB2D&c3tj?6Fo! zMnmRlh%?|kEAn6PmsjWefMJ}Xh`J~~KaleGH2I0+K@U}u%rBjsX(lB%t*n+6wCob`>#*$iiG`*%A9kOhDNNB~G-lr%x} zlBSrXNUTjN$y4m>*RLfJQW`Pj@!zXg@`x4)N4Mjbrf!>u7VCPpy6k!xZjZzQaLUp?YcG$5n}#pov-HgQ=aA*I zaAyXbc!0yr`|d{0eyyX=&z9h}3t)KHiG$zPSCH%?E9IS>{7MSg_|nA3s?_o6i>n(H z|BWw!py;vq&{ir80b4&(Sw&V(kxj&{$a;BG6!MOh{7lrZtA_4_vi~^*fm?rLrK`m7 zP-*W}S|noSu3$FmKSA0zJ?0UwX>lN>ypw5>XnfSOb!OV2N--N5vJZqW>6yrkMPtLh z{aWWxuDNq&*r-aV4kCCL09)cC9wef%QU7jjuwh^w9S%(HL~NfKH*`wr4&p0ZiT60X zBo-I;?Z}Kb4y_#RWm9KE-`pGDj@dRdsaIt*7a6<{gem!zjF-h@qJiy0ldYMVn?2_p zO^(yCIgNTlV`r)g-MlobRTY?xjyQ!w6nBlGxEvLg%6+-+x`8d5U!=*_Y2(nwZW)VT z_O`ONEsaNI@tVfB&&m_#M#w&h;bEIRAq1h&eGt{bw8)|Zx%LbdjPsMmTItS-;rJ{{+rDwkEOUx%1dY$yJG00{ z2!$O()huj>%s-TC?$f$^rg{@~1lPWG%&eXxcr*hczqL2w6?qC*GKifrZ}(JxypG~n z56-#GbNJ5@dZT7xGHZ>%zJHfhWW#*u}lwAWUu4v&+ziMd9TWzf_J4@M@nBNueN7p0zals{m1{8R){S1L<0 zyn@A{WN`?QqRlCXko;r-ut3OqV5-|t)2XWnk&aYWq&R-c3~!bi0D@{D^bSHNt(?$V z^eegAUQn;nGLK^!z0v$7C)l4Izn?qu*}OD!4`;^Pzqf-A zW6WZAh#n`HDm`P2m_%qZ8STrLS(J8!(@qH5Ec*Y2-w;YSVfV}WF8gv_Z*QVR{7{N>!^eTmLNUtQHOF!4G_Fq?o+b>Dig%r42ZY_3%B0?O w9PL^z)@3EZfW%H4MJP!rO0!o=QP^T9QH*t}Hcw~35@IhG&qzR(0Lecp0TSE^&;S4c literal 1720 zcmV;p21of)Nk&Gn1^@t8MM6+kP&iDa1^@srN5Byf^@f7BZ6t?3?(H4~5itSACiJs) z6;N#3THd@7rKIX&OLOWFItd;?TtP9jtYv2U!r#mKUP-B@~`_{ z(L^|{wryF?@9v6Jl5b#?(U=-xq+IH+w#{Tfz ze{9>fZ6{;fw%G;c*tTsOoo$p~z8~v-{{xr)f-Inj84^d(kR;x7zAhpXaFG95HJ%9?It2k0dFO1444Ke1ME6r7yv;6sHT7~ zR1jUkFtj(09Egk~$AG7V_^&+E*y+)uN4lP64a$#L@1*YL|NZ-@ivMf|0G17?K}C&Y zN0(!Sj%O)8P*X2ewQsf3iuozPq;SaUAbmHR73OcPY*&>Ydh+QK09Ypg<{+T$S$aPu zxZFkR_*M^6uT@f|UaK9N0E56*+qJ-@P-|mwMd76{o&dHMz~PckeX#d)B8iOBZ^Y** zfhT@WiQ+)ZV<=hohZwmOrcE_9q;ytL0G`J~jC7xBPlPP}R*P!#_S#t{`3+UlsZ7VS zbR8qorm{QJ_ss>$6drnuJ{Pc;CB%I1ZKC@Z6B19?Y3N_2fM7uGk z3GE)YFeKY`46i*A=#tMBzYNr!6{Xj<#%A5<+%s;XOSbC}23yjpT-`P@)ov`w{kGTE znL)wAEO71U(lcp#ln+_mBrhsQSA2G zdSaYVi~OnQPL6F;v#J7v*CKk73b2g@r`kkMRf>=;<_XudQEg?(FCG!4Ataf;}bSzqAk<-M~i0 zpnuI7zE6qOm}qaOX;bSkrw_4RoEIIa1@NE8+7t18N6wd*85XdX%l&x85HWt zr-~uqC50{iPKpxr#S{3_uGMa1ghl3N6gkj!i3W%9<+-;4i1{f%m8kptFk-zk)Fm%g z7w&rEDRA!h(KfZOrenrhYvMB{%gp_+v`iw$nv9>en`>c=79TxnF7 z(!xDe*`QO(CS7WjvO!ZeX+)v}r6v^vtqKTmHri&Hn_#E^T&~z_%^E}mG=zzpq|PiR z)`@UNQI3pU5zk_ArA^S^IvA*gI2{zLk76n#K48lM98F%=v+~EhtDJF-Gi20+aAS=1 zRrhWy@7=3n=ElX}Bg;R=gb&zOfQpbZN}N=%zzoV-%UDF481o!s9A=Cnaxs{fuv^*M z*lua;K8~I-X6yg72_LXafIuXmY8mNxmWYx?g%lB2m+z3+dLQ)i$W^AIiQDB@+iIJS zHH9Q1q^e;-z^D>qB*@)p>xZ_6p*@jd>}Z<0m?X}($T61cIka30y}$0fPXM zfFOX3)B(TOl`bznx&sR00S5rk|8JWEwB#cO`D(0gRs`;TfCBs{Q{4bYfboD%04CrF zKmkw}0H*=eO~C6i#rbcaJesF;sy3L`8Qs5Dv&A$G>Hf#U879AWcs>RSA@S)!YW=U2 zk#Q10L8S3+h0h5JTV*eZZ>+GaEDH3beD$jn!?2Y1-i4BPT>xMvloJMnruQyo0rD=!%nkk>$K8juSb-5h#E9TS0+NtY-p46;HpyVL7{;#0*fv*r OPn#DUPzFIfgNgx$v0Fs| diff --git a/app/src/main/res/mipmap-hdpi/app_icon_foreground.webp b/app/src/main/res/mipmap-hdpi/app_icon_foreground.webp index 06f5c8f47bc5be0f7e8cd0e44caca6422557cf68..e9f6b85f5244f25747db175d6d567a39b52217ac 100644 GIT binary patch literal 2424 zcmV-;35WJlNk&F+2><|BMM6+kP&iCv2><{up+G1QHPO|!?X>O8W_}Y(+qP}nX0UDB zwr$(2V>_K~+cw7Hrd5jZJx22Q>! zbt+Zqk}5}$oo;6Y-7bR}V=J~hdsI z0hEg2J-|PJTL5DcC^!`hn3+giUm65+fdD_nFldMuJu8NR78uw>pUI7xMY+}Bsh{k2 zoXKJhSm-){H%t3=p$E_e;0Z+#>Z`GiK!80Wh%m_B>!Gy}H6&20)w#tWz+)5C3-}2k zLo6(TLUz%DL@~?)Y)GIP;Dr>FmOwphiYiMG)Ri2dB}4`{!>JR5ra@gGz(X0NE4KAb z7|7LN$*I+Yr6`S>Gf<8Q5vryIqpA}sn$ZBf+_m{Go3V!&&LARYQ8Q*1={Hbv8P!~Z zra>B0V~P_d5Z6Eg0G>)AN0ARZv`V3B^ej>jnMEnom=2%~L^e}HsT1_eX$BEd3}@H_ z;G6Yz`NI6!U~O$4G>gctW~9u~4o1qN;u6>kft{%l1fj4Q&GK?cVcK$eO+GQ|(XyzJ z7)t?k5TcBkvBlAj#!{EZj5&Z-*X9fJdxN!g`OakzfbVwJ7f3yNC+Wm9fnH8AJuevm z2af6zjFCmOgORhS^NM?7yhZx%U0I>2tt0$X!&gP~ftFnmwHF*j&?91AHF2%$%35RcazS=`x3ecyEo+?hwXzUdA zDnVH@&62qR0I*4S6ed=6RW30)0ZklNBZ?Df^{IjHP^eWj{!cbb+jc^wVjGgL=}@M= zOUWmniP>?=$-!E!mX4}wpcFAq5zJt47sIRb69NHx^u2_>Tnv%9V6M!$D|GtW3>AAo{gyd;Cl5XgRD(t^4N2A$5Rxu-{h-RoP3gHT!2fEe38~}JL zf+(K<>{D}rE-KEs3k(@*BnfCoV~NY@#w%HVgpn40(CXb z*bAWfxyd5l9xyjyNd94-qqTVuZEPsSYJ_Z*a{NJ}ky~DG_R%T_=+#AcDNqleA}1jb zodrf$C-kO)4;QFiGJ#ID$W^_KFv|L8gSP`UqbeXGdr3gdXFH2l(DFHd54N`$|fKsWP3Rnaj!O`9-(wxX$vxD5MQY9L_BgrdQ zTT9U8{I|vLu?F&j!r)*Kprq;t_ycHndnu04%H`G)uD6wH>u7(CTO97KvL#Lax;^1; z^f5a-5jq210wvYxAOVnpR?hcVi`9LOBcPQ3q&%Ve2K6O+HO8^tti7zBYeCbyrJ@pJR=VmV&roRG_5l8n^&xW>$hQH~QqO z-$OWqjQ{%I^4{QEU!q#K5ykStG?qZ$KuI+i2nA49FuspA$nkFrA>08Sl^;1SKce~z z^*IGfrE*-57U%%T!TF{lsXs3vgj1NElf>MdBuAK@pW;u`^HMNO1*!n+0Ie)e7whpD zLKryajc9rYo&f63h(ccMl(%jd;WGQRdc@2AYFU17AcV_UU6ckLtjUw?-5f%=P26V_ zqCFT^EamF!kH~L76wv+q(v+7Ju0KVE@o+W?7J|nQQFqKgxoqEpd+I$bO2f%Rv_EzUe z{AQNng}T(q&w~+$l(+d%V^#R-9X2)=!UI^ePY1ANa+!x+6_zvFZz{tKYhrbub9SWO zWhMYSShZ&-hAIhMLG6+Wbh<^Z>K#U5xPceIRWVfa?CM#7-VV5bgxdr1*Y7bZ1vBg* zZ)TZUmBi3nOk=$4 zui@=rt+m{oZgHurlg(PatZVuI#`>}$%X=sHx{zH qz0}3FHWxz#_#=Up1$j>D?fU_40sKcq{OC9_RNlaIV8;G4b{`M%Vwku9 literal 2078 zcmV+(2;uiqNk&E%2mk1>P7{;f9$2>qEcF&N-A&+5 z`Za)5aN0ma>d-&Z!i~}B)^KRS+aQGstN=0=JrG$XlTiRgPzE(n0klCx%Ax}b(FQ2N ztvBn1=;Y7=6;KDcS750m+O|zQ+V8y|+qP}nwr$(CZQC|VandfMX;;tu70I@3o3=CN ze70@B7u&XN+qP}ncFneJC$*GP(jNqhH2?&j-?+ed1TVSn?E=ipUbEZU>JH$zMOE8o zwEZ!18%dF~OhSyv?qR6U&2|CA*kjH<(gY7{5nWzHh5Ov<*Jn4*yMUFA%s95P?wyF@`7;zd+rZ zBdQf_rBiyX`T!u1L}61bd?boh?wg1RWPOeN9zz_aTf@fF;>8O@aK$?yj3ZV6836x( zASeQZh!#jwr}_{#^wnOb!vk>NRSnIV3szT~=zT;f~;t{Oz(%2Kq8U^|Fky{(g zaIAgOqW3b>Q<)5#1Hg-}%OCuJq5b#w zOB`#TWj1UM5v*9~j!3B)hN}Y+K&i-?7;;P&!&&G}cSxj0( zI1A>#AyRQxdRZ)dWa{9o0s;sr6Ehbx4$5lU2Ev#z?}13g=*?mwf(;8Fn>siufYZvx z&Buy^vYEGoP-e`tFH$jPt5_IP2JkO>YI0k;d}Mbi1$vZ=Ux*P0Ww+=6$#d_DRE*s& zAOfv2W$F7?#8Tj2{LJJwbg?)3jZVeDY2^|ZV}o=3tL&DYAbHMhk&1CU#X^g8S>c&= zTk4(_wh$yOac*+6zhZCr8yyOP0K&>AEy0DtzM~<85wmZKRLrp{0VL2oRk|rHdCv-( z3Zl8N=4;#PpV;?0BM(xgd%IOI(e)191boWBAz)f-U#^S65dX$ct!(>8*)D>!#?qRwa zG8?yq#}~XUuCG++Q7UQ{lM4gORe*+#r?`7_&TQBM-h5H}T8bVeBW7Ug>m&67_!l`g zWgNZ%R{s*=)3Eggmzq((3HYD4M{I_L5}{Ku_7$9(LANpZn;02R0<|P?)8Uau& zXd=_SF3ayVXgZ^A?J@f^^EW2Fb^~~On62+Tc(@0^%pa`*E&HxAJ=8CZJqD8e{Qb9F z?YXUV*SHhH5&>(zvh$hKe(H&v11W6w#klhj3K&B2reFEQB?75Hn0ZC`(q{O!chc)L zboWr2^+fU}16n4@_H^S*W}}t>0P%b{aJzpd`EnOJX3I1)GTp2!ax7bXGbvd!*N#Y+ z<~tNo{hHahl^CHcH-)lA3!nlZj6GT;PZF;Sqy<4N4+a*7ODBn`Y{Ehr^cw2{UnZu% z(fygS35(Py*{xRjKECQfF(H&KN|l_Ansl61G-!fs79C_YYs*)?CZ?OKp>pE}qca3Ug* zByfQroA+In*QLLg;HDu|s~dLq0q5jxQM@VODHpWXct`gHi9+dh8%fbqIA;X{g4Tjo0<_NuWZnoN zK4jQ3AO#^L1-}2uK?up&ICfYeR(3WHxHIn-wodogGQ5G@Z6G5tyv*Z@Qw%i}0Kj-K zjIlpcTv1K|vc(JF$%D6WNs6=@vE{9kH`|7_DwHUc7gz2)nW5NpA~l%y-N?5YjUwsM zR%E5rG;tNtj8c|pWAf7DX^|(rywDb9rQ1`*lWx{%tFkjl1OTET_GrtBvPQ5b8vXwN I{eS5N04O%4$p8QV literal 262 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 literal 3072 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 OQs=!Ujp7NU69E8!|M~d< diff --git a/app/src/main/res/mipmap-mdpi/app_icon.webp b/app/src/main/res/mipmap-mdpi/app_icon.webp index 17f3912c51056e31c2632746eb8886569c80a101..70ae38f375bfbcd64d5202829b3e7d1916b489c0 100644 GIT binary patch literal 1010 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 CJU4a# literal 1482 zcmV;*1vUCoNk&G(1pok7MM6+kP&iDs1pojqYrq;19yzyd8{2bUeYR~owr%^fy4bd> zl&Wl&wvAt5bSYoI?-=K)^NjC#V!Qmtjdjb|PHDTdQ>KtZ+qSV+`$KHowr$(CZQH4A z+fGWhP9EZvkG=g%u5H`KCat~8RhezuwySNMnQdgY?H90Zn~|c<`VpW80)QaGX+)R+ zHlbT)9ENZk!Ci*Xx8&}h>=?Oit>^Xyc5O2O2mvSo*Z}whKsf;a?$%x9QUh0t z0N#k|09*seq5>cRU`7nYW4T&&dT*hUCsXzYie=8;D17dT#JwMKlx}xHp11380s^&r zq~VCjYB(&xRse9o1V9kLNI;06_c%E#cgtC^i-l@FSL-f``#-5IN6A)FjYp;HvCL=K zBDT@W9RlDP697fx>p4cjw$pM|?WK`2Ls#jEL=w_Lgj4|HV~i2+j8^FoQ~>n!MWPgF zIYC2Q!tQtR+h4?Oe^I3K0wbwJffiW6G+Y2&x>$ACuSl~Bwwy$Iof|IyecVUVO7zNW zr2VQOaUoOwp?nGW4C$*Or!;M} z%NUEG&!y2hO!-%?7<|TzH5edz^^N17dS4i&^x5QpC|3kNW5!yjDNGxkaz-NPb#9b) zIg~>2GVQB9ds(&+e8$Xmkb7>cQ%*iRABZj=c81#F>9c=j3czQ|TnDx2)wgVQDi{f) z=b6d&AA~welX=k>M3_CEK6`n9&y=+uVlnIQ+3Hp>5=PHcqqN(J=I^mqda4;&2ZAU?=dv5SxjP%uum(K){@i=X_dKPi14Et9{9X>?qd1uVPY^A;A; zFjvkIG5-waTXL4~B%}fqij$A@$Id@Nwj!d@VNDjYZ5ji;ZebwkjgX)!jp!yqsk_2*?116XYYE^T&oi*`Dvc z?_ep5P2uxTacG5x&sC`P6i%TWrQ7W)k|+xpM)!R0eGf}nm?>rnpL+uH^UBS-8>eE- zW7zjT_mE9SykEE+CEM&On227tu#$tBVy5u9$2ex-vX^WnAWgyzGlfjblM7#XrsTjy z0sfOA{q|6?Yj02^eD*OF--{M1c?e)$ib){5W+T8lO}V)zIj#xa zU*mQ}HAGZ^iMQ8Hy1Oo+a2iwq+y(|rrdkT{Tc%h(}9+= ze_6r#tzqXd;@k>_gNt<>NZc^eLMGHv!uDF@>Q3ixNwOyts_!UD+s(i4f~>uufH;8vDxFdyO$sMULY)Bk k0T5(amJtL50K5QrB1{1w4B!$#3qT4$5NNEiK*S0-^|5pDy0pKS$y1HAxo@|ZuC0Nk5FiYqe*?RKp zMio7SE*zrVBRJ9K&^Qd-q2N$AgWpl>T9#l$Ama*7`^f-T{V8BKSn_`1Y9t7q(*i)} z46cWQ3IHg;GSmT3e-_dNK&uW1WTX!2fJh`PWb^ICe<{WpoSwuvQwAz&Q2|~NDGftg z{;&gG C8gI4$ literal 200 zcmV;(05|_qNk&G%00012MM6+kP&iDp0000lYrq;1P~5g{#QoRq8Nm6r5l(AuTbnPS z5}$N{N@kR78E4?y4$uj3fap%astR2v;BejvK)P2*$U@9 z&qhO3xHO9j;#O8K&4Rb)Ky4ONsLtXEKrz2giNgS2BDzlWO=Ay0)!^}(lk~jysgOpF z382dlbFv8>yJbPLwp5)BBgUzVWSEg3fNI7BAfpcXfvSkKSWR%bC?SNjoe3>`ssaGJ C4O*1| diff --git a/app/src/main/res/mipmap-mdpi/app_icon_round.webp b/app/src/main/res/mipmap-mdpi/app_icon_round.webp index b0889cab4035f5b660568cf9235b129935c9830d..a837a3b4957a78d5ce4eb8b6ce4c7859e108c613 100644 GIT binary patch literal 1898 zcmV-w2bK6zNk&Fu2LJ$9MM6+kP&iCg2LJ#sFTe{B^#(z<&FZiHoFO76pbgbi!O!n7v}s!y;4>R znq?Z=Y9@>d^>jtRwQbwjGd};ZZL6};*|u$UHXp!tUctui{`*nKcJh7qOn_W#+fCcK z2-~)8+qT`=ik)<}ZQHh!HX~gsn{&>!a~nHdDyN#`s7`GsYgVTV%eBWJ-9QQlk|de7 zqJI8fJsaP)?FZ+!ZBx(w{C%-)+qP}nwr#u1FR;_GR%P3^oeTq!Wb_Y003)8a69Cf! z6NI1w=mt*UqL2kHNZe6q3k%^8{_#5QD5JuNBmm(mO2xExtSZ%8?fLqBMaioVd;vg4 zmN~%dqEOq>-ZfCc=%w<cZi8jcK)nX@-{JBfe-A=?x=S|U(0#dt2=nCJSe3IjQ1 zmYgyhI_VM5*b`^P--WOV3kp|}WYhrwg4&!aCLGZn1d04=FZ5AS8jZuilF!Bq2>^J` zTqK&qaQ@x-aI^suKm?&#!1B+G4%8T2Cl0tooWeM>9o~&882;HyRZP=G1W05SdLiou z=uBLv4sbpz4}lvWw=Jm;>m z%Paud|15EjIJ%BB0_C4i?h_7I5IE(cymM$O`=7~s;mnzAS=O^Rr60hNZwuIX`3}G1 zA8~YD83dt#jQ1s82ppuAYv8k)>@wRo54L1!;b43cW=bAl&d{ALJLI!;$AL8z-&O}F zK>8e?Gn4hp;Ink&N#`sqT#SyRST*K3Hf9aaVomM`a5FdftUUcBJEy?@<+1=}S!d=# z4?&E{oyCQdd4)T(+LC^qTn%|O%lf>fLwn-}q4e34sB<_DeEgC~V7_*a(p=&*o`EZ* zS6FCu&q@>@mg;qgbNa>bL2D)!D$^$VuBen(5)K-iR(Cf5Hdy7~n6c@aa30!#q(?jx zn_bVa#M+*PNID`}bXhU9Q5Lr`^LZq{5@r9ha7nxEZx5_-DtI#EHg^_z3X@Z2gANmm zp%0=gJvE3JW)XQI@13IKq} zb2t-n%c8oD?4-JrDL_ohxkYHdwU;O{DUysL;zL5xge3C#l67gHR4xNEc9{Yz@nT4l zh)jXa>qJSXY*PdKz7e;X6a7$?VWckl93lD=2`w139+Wt32FHn}XTCfeGYEVQ<-)A? zGAF=5@BCL*c|Bj1O39KW&qc{mA#zNZk`RYg0gBIkYpq?I@v9x%)_Rqs|G+6opd1+A{+|Q{j>a}A3RXCs ze3eSmR}ileO;Ep0R=@GH)o+LD1T(ZBJkRqP@}DkYncX8T&c>lWwNLKqKitpWA!R*# zL;xfS=5_p{As@8@pw;B17piJ$X)#SSB#*6`#2Q*!T3ViV^Goag3=3s(|}U|{6K)2cE-mz<|uzV0yqN@BQ60a;FjNRnXxq#?p%3FFBa*^eDh`Qe29wz;?M1fTgC zF>x`LgxRPj{ZyCS?a~S_VW0Dj_~-0|SFugnIsd88e}s8~jiSkbJcbYKSDfdfB9%X< z8x|Gu`RhXm$ylU;R=aW1 zATx4n8$Ymn(e^gsA?3;ITOJ<&;IMML@Wwsm-W5k7MuH@YbSp2uT*J3XWYeg) k^ZUyeE8o5$BNVcm+)6)i<1%TH&Ua5-zVU#dk}gaG0279X&Hw-a literal 2004 zcmV;_2P^neNk&G@2LJ$9MM6+kP&iD$2LJ#sFTe{B6$e4K&FZiHoFO76pbgb0 zua%qsCQNH!T!44LY;VKIrCs^BqMn(VnVFfHX_%$^H|hR$ckqRo)1gjoY0x=S21@#c z%1oQ;pE@@kDhp7mRLrEXL1Es2oT)-HRX8dGLq!A}*|tqvb#U){@7uN!*~o0$wlbRy zu>HGW<2h&iZF5h69PR%P+t~qPYNn{}Raw(Vq;ZP=T*D~S92 z7I4zuEkwPuhk0fLX9c%Po77YN%47>x2|ZV3Hu0Jd%0wp|0T`(z-JjQ&9gV8ruw0wBy0C!BGh>4Y5yeNssGAfkTwzBc!LRP>fiv}FTl4*{ZoEC3feO~B87hu;0QSYZw&mBr~HzR zSsvZ4HUf@-1E^yb88+!^8$_uSfD@DtoCMS>frs{_^NB_$K|?|Ir^1J5{yZM z0RTWa1xR*OGo(fsslK-p4*;+hz)eu&HQ|I@aTv$WLM~afr!w@ZuSraOLw2q_Kl8-I zmaV*3qaOeO0B@!IZVp8FAU=@BCj-bM0nA)0(GL+quDKYY=2S-gUnF295jh>SZUYK1 zC|bxc`~xIXD=g2ZxMLN&ihhX10#W*%%~jo%XpW(>&q{^OwU7XOaJ4iUWf431Pp-I! zD4$5mzXBy4eT76ZD$25=*o!gqIK;h7a)%Q}bctn?$W!aoh1zenbPCfdD-)nD z8{EX2xH5&g%RC1(VlT#s0{;&#sYcjA>@+PcLbJ+;aT1J*>TL7zlx$h-@z zzDttgx>VA00%o-Jj8R~$Mv_8I`IpbB_9JpSEt*hzmKp5TW1^^oi@Ik_$2Wds^Rf2$ z&3MFKjG47l)l?UlXP$Z~VpsW#dyIyPzXDE$8=2Q-;fU5cFB5Lg7~I4fz4C?nn|vk= z^vedy%^ zjCT~e^bQeUI#z-7gQi%%)(-!L=mWBA=MD_CO1{zsU)tx+ER1Jf1A=-lhKL_y9;2Bj zE*M2$x3UKs=_V@lLB(&Lxcex?UIGzO_M4}u_{vOpz-YUwff2wsQ71alW_+Xz(iX|= z4oqkRpQ%CqvvocM>Z3%~XQtGbBo4WYT49B17WdQn8fdd=)Qijt@ z>b8@6t?Z;rbWgic&&~WXmU0B9v15;vA1wn>LlM`SP36y~^u@Y}VS>(}<%digCgeX8 z>J+eTjRLwMv(!xll@U`hF_oYfQHhfDpO3L7&{~v_dzT@II4}ts<)8m2LEjceCE%l? zWt5zh--8VVFb(WkYI@cQzjh11_V7RGcM87}_&43h48xX4Zt9hd)`yznADS#{gQAQL zd;pSh$FydDYg0@Gu(V7Wd)BHjiKd)LQ%+3Rf&E)A{OQbz=yW<=6Hdg9do`qqF}0_0 zmM=7re}!^lRCdleeY^gU0N{g}ZV{6~aR7jz3QQv>PfQ|b9A}<*2)tymNIh{J$6YF< zNDQl^P3vfEl`F=vi~O1aD&>ckOPK|FkORQ!q2`J4U)C2AYy@aZKH8S+cFx+RyY%qsIV=00!IK*u3kNTt>BSS31 z$e~@I-9}$LQ~*)GIGf%mYpFQX$}yg{gR!4Ba;(fH;)Xft4}1@Cj(V_6m$`QBT`PZ< zT-SSA({o*V_WjO%%|gR)4pTE#uENT+b{OqUUT0@#ZvyUb51dr{MRClF+%7|#xSk?7jG%%RVl1; mc;xzDZh0~P-eb~7h|Pk<>?OxuW9m%vsq0=lb_r(+Ob7#lvcNw8 diff --git a/app/src/main/res/mipmap-xhdpi/app_icon.webp b/app/src/main/res/mipmap-xhdpi/app_icon.webp index 73172c04a1a85420ba2b7cde4ec043f01e4d2473..f7ce11aab3122d1c1acc0a34f72706836694d5f0 100644 GIT binary patch delta 2144 zcmV-m2%q<;66_FVQb|TeWC#ENS4BclR!}%hSO@?BFJHhH5cP(Fwr!Y(Kkl9lK}1Y| z-U94$+14e~`3o+wnLvN1S}g*tTukwrg``+qP}nwrzK3TdOZ=2Jaszv+X~y zv6|httKIJ0?)=tfdO&a^Ns%lpyBdaR8HX39mzj=nRt~jo$0I#)#mj~w-7{it6CcYJhKj3;o! zZDK*L<_h92w!pY zuc3|YmtxF01;}j!&XE{dsu^QhY3w(76yK27f3a&=h@{oeCTU~qw0hWFK1s7D zYgU)MNK(vn0=eBHfJ{5i^;G7xE41I{#e)4Nmlxmx4vUF@MTZ+EXl~b{P)bgB0J+=% z#qhA(2E)a6!l2JYqR6Nh_2k(IVj&r5LMGj`bP!jqE^gK z_B+5V<^OQ_XQ#)tieY$gb3n15EGXvcW*x)!f6{!}Hh#IH9kY`E4T37!Z@+Rs1>q|V zlD5eeYr={<>>$mXZKun1?U;QEOb1^u_)@*3W3pypqbt#bCHH6kY%5I=Pg2z*AUdEt zNEPJswUVC6nuTTS?H3W#Wk-|am%O9-^Q|;N+|3Y?;wi;*h=k3RPHj6Hdx{1T;y1`c;P+B67acczB}bOsqKh2ivH$7ScIU zd*1CB-4>Pq%Z?_-t$2fu$~@@~D!1zAf)xM5fH9fK$Yd`(*mM+Yp4C0Fi|#d9G__jR z)UVq0-M7;6XNRI}_(3xGphz`5f+v#0e>M*=o~Lq#<>3`v8az{B{iiwu`;D~uv)p8& z<+3(!zEeW|arSQxo~wR#UKbjou+zH8+ql zz<{Zi+jX699Xz0!UVgV)2o#51f$hqa>~9c42sy5Y5|W_pomg=&jN4;lwh8W2O;6uj zEd-1GHjip}T2>kq z{i@VDd_cFnysy<9U_s7ZiljbdSe{QHhJPvgy*>;)pHaw z_dHh$xBX6xRoD+!?x(OT;04iX*(11uHn*RU z;b1^02Drq1F9oUAN|ST+|{gsnG7 z+9qojrfgT(!uSw>i!R{Op*x|~Y6EY0RB3b&%9+wt71h-A_`wjL-A zNB}%QY+gVeK75!2^a8zO+koZ32%tC63t+oGKqI1BJWyz`xm8x9XT1vm_U;`BTwow} zX8HQIr#Y^mDaKXJAhbbVJ;v7cml(K5`ZL-9{|{9e_#@@8aTEcIK3DE*f6_- zEV&yQu(PtiHejOFXdOA3f2wlnA@Xn%XHC}z>z9j9&d?}Pbhs6zBTX2f4VFIJ-_}15 z8~#5mhf(E?uS5^X`R+Nh_3%Oc+}`j<{sgcgg^y#e2yyv`!hx9Mc?CXcNYI5{OEa#sXd=9t-;SI0` zhw@{^>7o?z&EDYf{~KraJ~ttN1bh@PKmi&sfRX&s)BC^u7!kA20RyBERwO?{5GEx4 W@3L>Z|G5}{ei+bx^8XG$7uQlqa~<>m delta 2334 zcmV+(3E}qa5U3J=Qb|TeAPE2fS4BclR!}%h6A1tSFJHhH5cLLwwv8k?{Aurh2OuIQ zKo;AEiJxqUPAf{-{uz>_nXam2scqY4R*h}jo;lgJZQHiB+jg&Q+rAYh>xnq+$+m6V zinVRqihcIzy+Dv;TWPKCynA`LJcuh2ArFbmcUM`oZ5wYm<~)`&zD1pm^7*-T0_n}s2o2r(!|B`PGpvVAAf1wm#) zI_9y4JG|oMjMVqf9C~LvLS`Ws2#q*I(1C#p4Sl%KOcw;0X+BdQ8SG2@VZRF8@zGc~ z9?l`Nh?NBd?VJ4{SK<}+y$r?_1^l939BUZb?{P(bIzFY7OQv5ue`Yc%*x|VLr;6K; zrym=PWaa|IC^NgOWagvZ^PCTm!z@afaj)?X@BymJrj!|vazCKfqZ|m#1F8ZMKmed+ zdsf^z(639>ueA>ZQo#hWZ4DF$7?4O)Mds-Wm@0L(?T7aELkG1E1N&@sbiPC zYB#W>Ochi4J!^P6e_L?s$}6wT6r3|K{uTiM+YQJJ2$_0u*d{bX6V2Kho^x9DzxL|? z9qU{9r^P%rS9%xc7paREi5r%zw2Hn_MN6$11=UeU0Ia%~w7+C+c=N!dp01ZUkRNtrV- zD+X->KoB`&8>5?C=M0twW79Io<-KMLnQUbIVu=Tz5-$}FBgY)4v}*tDnks%b`7Z+; z#!gaK8#*Ole=Ch6V}iu2t(yIq@~nna%Y4+9c&~OGJHw{*Ta|HWWMckoosS3lx5buf z_ET!L{|;!CeyuW5rbhg0L{=Sh2%Onu1_XClTkVr~AoabOGL`ZFQE63LMNg~gv4?2Cvf5X#S zgr?`YKF-5-=ZAw%QOuzB4^nuccNIcD-3Aw&%ETaKnu zD(3NXx2c}qV^S*Rz2%$=nNsi7W{J76@oUh$>0UG|&oY-^t*lQSiV-fnB4JL;;Z#b+ zj5!9*e{ECUy{ELLcFU}t-mx&2zX!})ZbWm63>))P2OL%rfWzc z%%@Hyx6H_M@CIk1%y6eb$ivnIRI2{9b2^Ncf0X>u@eRt*jVfBa_OIC8N|317;K2C0 zv|+krD1V2zHV|JovS7;RAUtK*Ry0wrqS(#dX$ z1j^`=f&3Hf-dem&e0JT5)hJz{Na#Fvsrc1^p^LS_TQ+YC1}6y z)1H;f=w~-L5lz!snuYcQ`$|8}7$#g5QSP%|1-}=e!v%trH#(W3nFuAf%*a!4(`=R| zf!)CVY7CRd#g3+T$~aX8SNm&IR1tcDe{|AC&;H&hUHH;r=ve8e3B!f8H9Y4ub@)An z4i^X%NRF-CM>b3sS>`*AoGN@ZU|8X=!b_HjNk0Aq33FdB|5=COg`W>y6Czf4z1&AF z)_8$;##)nL`LcC(+YE5qPb`mIV)MRHYm$U4MSnKHE6c`~q3MT1tA-&UJ}T{_@; zp^$hx5BtqpZ-iInpO*Wxgl8g4B6bs)TzMmuR?!Qq!{B9TqMfI1#>!YfFc1(`Y-?I| zt*tv&>ucGyBBhd%@hif+%6V5Vf4}_lQ1^zE*`N`EY(I3MOchG17)QotiG@xXOARz3 zphpzzzOAB(4lYCdmw^F`09_hIM!=cGW?UU5_jM%#W{~qKfjm9rhQ@D0`TJ=?hzQ6{ zNNy_Rv;c6SC1Dnuc|bPb5G6_hpLIw=kZ80npHLP3=^xw;VCkBGzhTHPe_)CMn+lME ztZbt1Y|Y}s%~f98k(eamnqyexIrtexcuB$1JtFqLie1qX0k#BS1W8dx$voX6EZy5& zJ=k1Tdx*Ju&N+w3)f+UoW(g$(u}#3U&t~+`bB1(h#o1G_%Ufc=b^uHPmx-Hi;VTs6 z03jSVkeSJEGC)dPmUvGb4&1~m97=!z zuGs)+377$SfFl5SO@ZaWi8mbsBm$|xP4HT^>jP%oh4k2!vcEDQqS2@wRWkWh?BYY@ z;ULb^t`6F-Dn1#ee^H?5a3hL`>M%g-FMZa(wSP6N{Qs~3ddnMM5g(A_-4kXj-~;=) zz4A}`+uF|ubKfG;50oM-A0QIRASz7tFXq8<1O*!NVf3;f_dAO6oR0wQ&jxj9q_xsF z9e0`Zob<3r{mU}``k|>B9KGSo`9}hu11>;#1FX)W_*hOlL@OovW~+1f|BW+ypNk+s z0^W<~pa2aRz$pFE)BC^u7$C9F0Rto#mM7ms;3ky(zst7m_U9!0`C&lYrT=&MIc-=9 E0Q>NOoB#j- diff --git a/app/src/main/res/mipmap-xhdpi/app_icon_foreground.webp b/app/src/main/res/mipmap-xhdpi/app_icon_foreground.webp index 67bc1941a14cda3fb3d8667f1527053011b31d1b..dc7a25de232614817a2de741319e66cd2d2a45d5 100644 GIT binary patch literal 3280 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 diff --git a/app/src/main/res/mipmap-xhdpi/app_icon_monochrome.webp b/app/src/main/res/mipmap-xhdpi/app_icon_monochrome.webp index ee458a14670de78282c7f2fb616559ef3824ce9b..0587af88549cfb4ea9dd027c2abebd300a27236a 100644 GIT binary patch literal 464 zcmV;>0WbbiNk&G<0RRA3MM6+kP&iDy0RR9m*T6LpaM(za)Kva)A8>a@!|#oNbKAD5 z=RU-?Q?a&f^B;VyW81E5+cw(IO48ak@0kFo{9pbr{})0L0`(A(6WBASQz^C})|98C z#99!xGM(n^5s>5Q;ETwDrV$vdb!S(dBxchvm7vb9Yp@nJL^BDb8L-_Ckt$y3VJKcI z34DX?W{^Qh2A!R1;)EE0;Hc3F4Ejn6!g?DN)g6}781%C!9bJ-hwvcSktWgf5m4kDWRdvq=AT3kdnkDXCoLYkc+$)T0mV;O zisMaArP7CvAr)N5tDN9ag^nY2SsoWW$)*k+OBM$G0{~!yETF@Q5QgU)PdEZ9|Cj&E G{}n}DKhDhn literal 340 zcmV-a0jvH}Nk&FY0RRA3MM6+kP&iCL0RR9m*T6LpaNxFWB=P^>ZfEug;JGIeaBbT* z_Uzw|(dHr2*?I$3PAc2Bjnb*o+OBNdw(gz)X#cnW+y76>OeIRDvN)j8I+ z!8{qRKC!a8U>G~a(k_A#hqZ}d%of97JZ~2Nn}ulqwf~w;zAfq3S2h~!f|LD7&}SF{D96)hyf5~V!Z|co+f_j4AYS~LDUGK9M_ux5QM)cf??{SmaAZZr|(i4 zlL0=i3)x{64X}`9*+&g`+F3)oB$U(?E9?aq=oBn%#)%ltQtT_)&K%`08KLT%D1AJlUgN1B0?4< z$);^>9oy&Hwr%s-wr$(CZQK01wr$(mme8PQfi1uhvGWpL=oBBubqeM|{dF-0IPaJB%- z;Lre7SOqr!j2cK+LYSCrGPDj3IiXAB?itynIMlE?mXTA*HZT+7L^MD~N=OCN;6%T0 zBV8gb(FiSv0%*SeM zgsrgMrLJ&`*L?yaiacTJPuL~^U^ceF6Y*|*AOFGmxE^=m0X%fJ2VMx*<^S^m|8D!{ z9{_-93w_xF0I)8ehOgsn+>ap)oQEJ<&iNN{){9>LOaKtJeuMzPB0LE{#VwL^#zItx zv@F92(Z25+hGkg>V~q3n!@Czi2neAMApo!`-j7Qq=gctt5W`}2eZydk^XL8gz5qgy zXW9VR3}3?ClDnZX-({M+{=;3yfAq&C0c?{n8vx7jVca9RVYw{Mu4OR(#Gm~iz$VGe z1HjX9rR0{uh%_w5ACG4MD0A&rPLYaV8_LVIYr@QnagyX%gd(>D+N0a0z~+`%w@XHWo3SL z&H|xX8X)vQTCTp_FqsAOK30&{Nz1fRO^6 z!5+g%->yq@pXb+c7G#VASp^uu!+^pv9V?bmNPq0%(TLQHJa0}KEsO||<`_C>b0Ri1i3f}qSp+!`VG z-j=y!L4~Pp&d`~Xe=xM^tYm_OIdKw)dUH;y0$jPW985kJ?3e`1BMF!DfW^FCdVr@j zjP`RV^RX%Pw$29=ddpD4ao`ab89S8cdjC z;#=o=8W3h&ajNUej>?8vjl&(>)1oDCEF<6}W$g2X>1VxZwVibah{xyZ9MBrZVnriV zg+0<|+Asqih!~|j=D)%*W}z6v^ji8O8vO>sFulgaN0o>o4Z{jUg*+f8@^>#|%JII+ zk5MWd5$fKo(T`MU1rRk$FaevT{l z7W&m5;?W}8@B@>@$kH(SOl9X_nofnsJeLs?{a5-LQBWaT+jOn_L}hl?Bb>s1IzzuZ z<49$=K?2JU7H`F7FcwC(IK1Z20xEO1g8CteM+qnK4TRQc2lnqm9E{)k-Juv2={UctU{)k$W`Uz5|OZ}{Z>0HMNQ*T9%7eXGJE_?HQ_*^zyO=cD9 zW<*5&>DL{_5dvJDYIk*};7mxhUU#`r`yFH$-V{*c?4FS$iIq>ukJpm7?}+{&)rb zjjViMW!ySJn$UA6s+H6l*msDUt;2Xrjy3#8~0lJ1--X|9y@WZv{piWp_z6;FZD>U#g?Do z31XQhKpGIc+)pB`;}kqG_UIlaq3DJ}{Fk16uS6(a_P3PdIaKk7X$c8hTW|{xB;2(X;Lq3+0K2 z3op>u?>6&`2j%P$#X_l~{#wLx2z0E9RVok(*mMQEldI1G2-T&gaupFU`mfa3=Me~g z-9vpN0Xk64**)MYfUuqwL5W;9b{ibsvo)FmE(=|*%52`Y5X%*xj z(R(W>9rkD#PiTg%|3kSeaE1Z@&z%FNbWA>cBe@ zp)b2@#qGG?w`B*T{`4_?9-a)rYdbm=85N>-9s2=UxAMCy^%x?mpWHoC7nW682!d{U z!`MfC){d$eFNn(A&#uG^4iU4o9n~q)QS>WTvw$2J?N>^laWI^u7&d;kCGL>p8kR{4 zBjAq%7RYI9OPMFy0;HWC$jjvr1*81*Sw+eI4&^aQ1>|g-reQcip}!bL0W;W?1zXiR zec%8Xb&z^0tsior=vff=@4Y@0g8sH;;OP{i@Ox#?E z0`uj((Cw1eeXz6Hh3&gl>0};xVV~`vjvME^O7O~#u8S0Ko}8)s@BxL(W-}2BsY<8o z!TTzYKzP!dbMoRmy%9U12(SrrkzVSi*`Nasl1+bu$HVWj+&0g&h?A9Cfp}r|Yp7={ z_ZABYO{lYW$G7kcSvQ~?w?I`p{m@TV{vbpgg!A>G=5gj~MfbVpagto!hiq7{ge8+b z|9^n{eFYdTA-*WcH>`5%-EcrjIX85^`s>|tBYh6>4q{urj(Hl987Wb5e+Vbdlv)8= z!)Wbc46;4$lbG%G@m$73UDe{(5wHG04<&yucsk{BVuXh}wc6ldTEZhPh#A@NIq8AzZdIK4I= zG?=8E6WUw(@ek(ox%yi&o4b$&+K@o#PnfThfQmvpwNyC}}*M)A324gecY|q%Tg|1(bS1sYyoE_MGF8 zUJ&>SMR#6}LHg07RmlK?Rx1ttJ-FOURWxuqBs-|@e68HmU}+!&fZ;~WKw!EMi#pB} z)0Se`uu=?Lm8wUIRaS^OmulEJb0!-$tFXZd6K7`(8G-QRjR+G z>h^l|#lKO}K`Hh!Te6?Agym}BG#3yXkOX+3+3HLi%-e4`IHyiZ0XPAqi(P8KPoKPS24YqR;v?-&J*KOo z7D*V;2B0GV;-L8$Kv0X0Q?nzVx9?S`VgqN7GGutt`E!!oBW4l6J8Y*V}@av1ezgT<11N-ox7u)oN0%^y0Tbz(=dTJ1QGnbcshq<)f%wHZ3K z9k*KlN`U4nvojSkUccLD$!+TRdhzPC7&>izo=^B#Mrj8s)e4y)m*IZZl^)dt_2gOu z|3&WgTBQ`kQx)z|gG`arATJk`7A22ba#rZ`4RSvB|1jEFPz-g|sM1)RHYmuRg-EX7_weN)m{#(v$)cRRRgNVSp@3PPB2XlDU9a^O7Va zy=wBkqn93Ub4fwdg%Sz~by{ytvuQJAyK(OxE>F}_m;W|Ic{D!MGkz;qnXsjstLHi8 z3O{M>0J$plh^-qIjOEhm* zaPyxmlyX3)POAQEwxGWe(_&=byd95yCmds4S7-WvG3>uFvnQ&Uta0kr={xRpyU82B zbcf;o4e-3{Gsj%J?}SS>Z-}g}ezVuqPdT7tl|Y=YH@fkb%o-OpELmyw)wUG4%ZR-v z9BAA6Nk&HQ5dZ*JMM6+kP&iEC5dZ)$U%(d-RfmGMZJ2~V?Cvs%hzS5Zr&I=5 zcMQ!DwL1$x>1Nx$dQ#@^Hts&N?(RD7?mmCq-QC^Y-QC?`+$F=^-3#E%IlnVVOzMPN zz{Yf7EzkoEY%5R<85L{+P{m$^du@&%ituS-tAGM5a$;`*A}8{cYqJeEL?ZnaKovU$ zAQ2(qhScB|xw}N9E}>0K7g_{8tmKes8(IYokkf|y4<~XeunIQa25=f6S}TFvVx2M^ zNRnjQih5@E+5Wa|+g}zW$);`fwQbwJ=i0Vy+qP}v_t~~>+qQ9axXuS@QRYpMam}6t3 zG6QUQ@oxYn!>VlDks+4}T>0da;S3-d0L#YRGAvp0)d=FS;%WkK z4kxl@27nDNoE%dwDys$%l5YTkJAft`dF5MC%t@`1ArT=9l4R4ijz z`mNvoMTpAtxv4#Fn*e|r*aXMo-S|HKgUfL{9>NoN>O7CSByKDC=Oh2!>ODUI0Mq8$ zvN-@?Rh)#c<6=CHAp(qrAy&rNI=JZ7Z+H;^a9cZ^17IGG!%uOKV2si*7Dg>g*TcB) z`?_vfmQE>U>=W>jg%Jrj*9PYRSQqcdwSqCK>wcJ^id|pVDP`>CzkV11=j53-0M^5o z@UUQRc+PjJ>aPC;m$D!IacKbCB+LfDB77K+2&P*ujk9a%ls)Tb{|B&1GV=j&GHw*i z(kYR;McFfO3P8R|Oqv5$;OBxp!KcLfPoToNJMc6i8~}%&OHhjvZBce}1Ar4@1BCYo z_5_y_?mmIB?7fH#HUT#9@I%2uDAiydWm%7gpn)B?d{{X0L-;<`r0=t=pOXn7KTbAa zVI)UbF4d@Ov4wJ{5rZ8U8)o4}vPB-%a2}gKX9ywKap1rs-$fqPbRM1OXGe74s-*$K z4}|3^mAX$tf}yi+Q|eK4OfP?e}9&<(H?4mW~^V2;NU(rR(Y-YtBW z#y%<_1o(gmKqnqL@lrfhl&^_JDbOXfYP1#(Y5GkkfhJxUlO+TxN%>Ds{+zC?{Ke-r zHqb4AgSrts3uf4lNL;0S$PO0Fr?H0tIRH9%BrKUEMYHUEZ^b~3vO8tDKZ>Gse->B! zt*r51Tiv(vo%u1-*6gUxDuqLLd@*~er!a}*2;iYUn;#~RgnU(Om{$>LV|g_8=N8^i zG1AC83DQ|a=*+{FBTdTfS%g4lZaGtW?rHmAw`a7mgL1iV&ni-s^Z zNBfBCV5?x7GW1O3(byO03Sa>;uA;#*+GrD}I}ZnDx``JvB$EOU-N+CN;mRZd)kOO- z@|>@Ebmnsic7gE3Y{NsDK&#knnRQ+SR0Y*UCmJ~4@)^vR%y{k|ZZ1iT>era`y0l}m zVFvBU0P{$x;a}@;$+10U*6^#rvyK80(CKSVDu#4!wwya!P?wCjzr_tH3Axl@b+q|ryMp?iuS*!O zVm~7*M2NXzq*Q6=Vw%nZk69ii!{(SF>nfFQC^zaiaT#vu(L%FLTr5eL(k7euc}SvG z!--!Zs%b_`!+6SY2lL3VIiyKhvTR~CU+4(!+ri+8z1Pyc)mEdF{JX5#CZUFZ4N0Du zv8w}Z=$RTVU(OaUs7t~;e^-ABCY)4 zD8d%IgD=#<4ne(i<$BYpbzsE6rj=5&g7SSE1@-%b zjpl}e`wsELEj*fR^ltN-=_VnL6fyHich++GXw%0C>c`LSoGtIEu!!AYZfr^TK*yDm z2e)aoKHNgHLrwo!y6JpR*nk!=b2=rK&8&~EBEuJR!C)EE&cU78cQD^2&GDKiWy=Y8 zt&!aDIuN#1s*@4F<+UG8)CYPQrTp4@yHQ>LASRX@&W(hPXg*6y^jR9 zaE(z8I~aE1TJ>v9{AS50N9LX+4UjyF5w3gh4QIj}F`QhOoazR2t)9BJFjz)$E9&jsM*F4%>7Oi#bZ6hUc>c zk>iOO`c7pY#TmpTG#oFTS(yDf8)3RVijo!0SwvZQhlc6+z)jX2tQEBA?1N1M+;f{Y zR*!X@uK9?xnMT^Rb)pPl86JyQo}s_NA`%O2sgdcim@@T_mVhwdzIWbUOfghH8<98j zc)Lzf68%`kZMG{tUZq$__%5$NBOWvV$gmyneExH%A!oi0*-j=?<>p4wp>5|xlwQq3 z!|^Id9To+0eH2#Mc!wcI{)5l`g@yGTX3ges$wkqj>gnvGb!?d3Ll6?a%G48`=SC=gS$R?<(j0!TYM7tdEh6ng3cwOD&agj4t`RZDcZnkYvV5g{HxI-R6qJ4$Cw z0MkMIIe#&t(2>=Ds}SDfk4~b$bPRTu1Z9lO&Y=vpvIOw0=doUFY( z7BaP5sJ!+?3;)p7trr^#L10xDfWe;B{}e>+pZT3Z=P~N4roW8JJqpM*^iv4cDBEMY z>!T24ni`Gjuw1tK;H|(RTmIKvb4~B{#7lrA#ca^g>hE;HinbR zbW{vAimu5N0@N*1mK;c~DoUv;pH{$Bkl!gFo|w4vz_u@I77Y%{MDi)0(L8nLPzcoa z<7r}Yp7Pd*lf82h5XOVgI{15}cp&6@QU((CnQ2g0mHilkUo+F!nuci2!t#s*NzXa( zJsLz(%mQt${KBVlTN5GM)SIo62CC#wm^WHv!TqRc2tuc0)o#_KcC*~zh{&}^K4$o? z8b!5dVN#BQG>DKnEeSSY7pB-CTHg8Ei}Qc|rL27sBht9eXvSbRrZZUt07;C`3B6pr z_9r36>)-Bhj&t`gPlcCMV(r`mzx?$J&SWZMT9ppCN68WpslPHF8ylP6xu?iv3~za2 ze`$1YiBAN8CnoMYl;`b^#Eb=zUx!pjyWZSaHZq_BteU0kLG_@p3V>^%JXWbvr4066 zKt`38eLlGA>trl&mimWrowJvDipB&|13PO?f{(UfDpFyLqtM4u7?|TJ^r=;Pfyq_+ zEOJk^zgsDLl))y@b_M>>y5SIzkczysv_$`}|M^IXl$|E=o zfRIt8xQDB+`HhSXDlwMZruj5AG=wUN84eHwByG7eY3p|KfvU7Ek1gvd>5lw8j%leY zR!id*)g|Zv#_1K8u6QLO-59NY+qTO+5ERNo@z(8h$4KdOi z`OP&Vm=b!$_F-#4on9XB+B6W1{0t#belN-jz-KIR#eP10=~5X`EDwnw9505wy)k|X`zPZkTG9071L z2hao#KAD&&W`xeqH)xRpRS|g`?2Rjt@!-!}7A8p<2#^Od0Q~}swF0OCseqdagowiR zdVPb8t8WdF>VUHY{<_Fwgr9YmND{LCV8AFqOw2c1c>^){lS?!m@Gr7l_lFLso!F(Q zMr+oiWYt+BNe0;D5-kVIZOyL6zY?Ij%iEphXQX-{ zQ!S7VvKjJMvG5En7@kNc<=a(kNe`KB&bjN$Ih>zjvzZI*D)6@CY3!i?apOia5u1q~~dzwWB`wZ&w-niW#Yc3ufBn`EUWbj~N ztoN55?(aRMpy+0Zmy$rTOq6d$wrw3`RI%s4&M%Bqd4oDkRPjAOX&A8>M$-5EDnhLK z#=CTOagV_jW#6q=o{&Je%q+4c#g25#3R-hK;krKGjn(?bD>fHGR9Qj~g23~FAkden zB7`vNTyU(G3vAe$Q$gAJAHPUOf(D(`B0srRQRCLEcIjtU1W5H@x8uOUsJBbiG?Oy4$-KshM#kR2HR^DS6o<&%4ytq(8aa-R^d` gpCm5zyhUDCl5r$dR;6?tL``HenM@+#I8p+!073TCW&i*H diff --git a/app/src/main/res/mipmap-xxhdpi/app_icon.webp b/app/src/main/res/mipmap-xxhdpi/app_icon.webp index c04de250e578b8ff44c16c03e65c5563ce2c8351..e5e7fdb3cbb6d5cb42eb8c98f70fe58986170387 100644 GIT binary patch literal 3358 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 literal 3574 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 diff --git a/app/src/main/res/mipmap-xxhdpi/app_icon_foreground.webp b/app/src/main/res/mipmap-xxhdpi/app_icon_foreground.webp index b14b02857926341af656d3cc3eeee2b698bb9061..9cafdb0b2a24d8aa3824d09b3065380694700f8a 100644 GIT binary patch literal 5080 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 literal 4206 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 E0MoAJpa1{> diff --git a/app/src/main/res/mipmap-xxhdpi/app_icon_monochrome.webp b/app/src/main/res/mipmap-xxhdpi/app_icon_monochrome.webp index 2e0651e18c4271427930dac4ae5c6762d29f9b91..5e79d3ca11e2a2812042552af7bea1cbbb0e8a5a 100644 GIT binary patch literal 702 zcmV;v0zv&!Nk&Gt0ssJ4MM6+kP&iDf0ssInL%~oGfY?Zq6e;|-8wRFf)pc)2$bux< zw5?ei*S2lj*0XKf{IYG^wr$(Gwr%XQdIHe+fBZlGAODa4$N%I1@&BLqj19Nn15e#| z!x?+6HODXsT$m7FhRB4g1jEd+)?R1aaNkpR?J|%-Q^*!6gJp_p>F(KLoFHniKuw^L zHn}HVH3gOxSxD*#3oqebE#BF#4_m^6FxL9m*|Oq*llY!xN6)607@JdODIDSW1nIusF6!Etbn5ao6oug zYxDeuqLw$q+bRHR9pS%?ze8%FtT)Y2TL%DlPcm@>QL;NY6jsv=yb=J#Q%TrOm_&~# zP#SLrly_eUb^|8RZ7AP7(CPrQOHAz30%-wIVFG!HWSnw%V*dblkhnl}2#3r77`b2+ zJTwBHEqERrr3*@fqhdkD_;36-{=2SZ;(Zdu^sUSO#AODU2zFBCFkUtS}AVCyy338X%3}qil8GF=BM?R4oV?4q@%=q-#BBNFO4Mwgr3R=BJBL2BH$OkeJFQ3Q#6Omj&IUJh5{n(4d< zD2w#O*(9r!3|l17MUlh=+8`S*fy=T{2!?`5ODC7KZ`10MuC!1*Sz97^i+n3sp^VZs z(cjx-m`D%hlxV1*xAX-}-O;_i@Qj4=ytog*cPXgd!mrTm(X!9B{$#BEE{m2jlCS zF&JQ4;KXGr-+*Q7kxM%9xH(2(>YhmHM`7-oNZEzSTOu?G2S_*^AYvXTh$oyNomZ%D zzU1@`ZOi$Ee*%a=PD1|vmF4G!Igr(CS^iuY3S+P=4;Q}Di$d4evP@h+zswuaeaJWR zs`f*@k)Ji^7c5KM6@<{Y!wh`Y#o-A4$C9Y}o((ApsLtlOCDKl#>1qkk@i7>dMNNRN z^IV!h(PTCHnF*_fs3`zwO0(d_|1hXm{j4#dZX8@(+$V_tPjG)62Q+^JmBYuM2a|e* zD#QTZ{tpkT00RKv?ceC2ig3Xxz~8|^mE(ZF1z|_VB!m!Eh5^?CunR{lETS|F8eo|DQks05mc7lK=n! diff --git a/app/src/main/res/mipmap-xxhdpi/app_icon_round.webp b/app/src/main/res/mipmap-xxhdpi/app_icon_round.webp index 9f81e3a63ae67106025e9a796eadae076b2ee78a..af8653bbb9102000c050d545ad87382e0279f779 100644 GIT binary patch literal 6858 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 MiApPx(MWeK3zqvaApigX diff --git a/app/src/main/res/mipmap-xxxhdpi/app_icon.webp b/app/src/main/res/mipmap-xxxhdpi/app_icon.webp index a56c37e3b37313c85e15b6eaf4dbefbe4f1ccd54..96f71076db0478cd5e6d1429ea0c0bd1054c3106 100644 GIT binary patch literal 4422 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 M=}(OS`8-_60T5AO0RR91 literal 4702 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 diff --git a/app/src/main/res/mipmap-xxxhdpi/app_icon_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/app_icon_foreground.webp index 511230fb26b3123658aa7e4f0b93866f65f5d3cd..70b4d01db0d2ed348578bb28b1e8c29e99a66857 100644 GIT binary patch literal 6634 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 kEP$&#p6MdP-W4Bnes?&v9pnR%c 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 c_&@%S|KtDoKmL#ZqqCdOLt2mk;8 literal 656 zcmWIYbaU%qVqge&bqWXzu<)q?vh~*=%odoS;5kXf^xyyd^9JkzX^{<61dB4Ft-rmf z+;6`v*E(}<$hO?u+j3{moxCw!D{NbC?a#XZ|BV?J{9FJ3_5WY{(Ww9DQ?H$RTivGC z7A*AZQIC$-MaRPOp2o{U)p`t5rB^N6`0&V$qKQ`w%EbA4&)S>x>}^V}_A@jJx!YJQ zeAT$&V+i-V+H(aObznT)_Noiom1j<@{z-FZh2<2)BLCGHt5mj|s~- z9_v*;>D~UA?U_ts+9`F5;w+Cw+t%-Vzvc1!`Brw|=Or{sZj3q4cTaS3g4g4l zAK!NcCtIBBOAp_`%CE>_ZOymNp-0wsSII_Og;yneS3YfNU8le5I3HV@`_ZMr57qY_ zGQX%^^YFWbgxQVTOmk-T8W`3TxP8_&)X-jRIbXuz^5K(D+}SpKsnc7(GKFo!*8;7W zK!vR(IS+eVmP?6BH#~aI_RwZuhil`DqHE7KZI^gt*zu>pSv6E3UT4#k6#)yKK3$%$ z;L}pgU`=drMOV_2vFYzk!JZVB z8jTzOc@Iu*Xy|4#_}!?>7$g4Vedm8^#yhqPRF%ye4C?vh*0er`!lVf^jKH+_ZnM6S8=KlgV09W!)00000 diff --git a/app/src/main/res/mipmap-xxxhdpi/app_icon_round.webp b/app/src/main/res/mipmap-xxxhdpi/app_icon_round.webp index 7654f8b6bf480968304122c4788e91d40d8fed40..f962189cd9212ca8e9d1a6108c7a99cb6197d4f5 100644 GIT binary patch literal 9394 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 Бібліотека Історія - Оглянути + Огляд Налаштування Про застосунок Допомога @@ -61,6 +61,15 @@ Щось пішло не так. Будь ласка, перевірте чи файл цієї книги не пошкоджений. + Хибний формат файлу. + Помилка: %1$s + Цей файл зашифрований. + Цей файл пустий або пошкоджений. + Не знайдено жодного перекладача. + Не знайдено жодного браузера. + + Вміст книги пустий. Будь ласка, перевірте вміст книги та оновіть її. + @@ -96,6 +105,10 @@ Перемістити цю книгу в \"Прочитано\" Назад в Бібліотеку Скасувати + Копіювати + Перекласти + Словник + Ще раз файли @@ -106,6 +119,7 @@ Невідомий Вам потрібно читати більше! Так тримати! + Браво, ви повністю прочитали цю книгу! Змінити обкладинку Виберіть нову обкладинку з ваших фото Видалити обкладинку @@ -121,24 +135,30 @@ Головні - Мова застосунку + Мова застосунка Зовнішній вигляд - Тема застосунку, темна тема - Шрифт - Текст + Налаштування теми, кольори + Читач + Налаштування шрифта, налаштування текста + + + Шрифт + Текст + Налаштування теми + Кольори - Мова застосунку + Мова застосунка Темна тема - Тема застосунку + Тема застосунка Сімейство шрифтів - Стиль шрифту - Розмір шрифту + Стиль шрифта + Розмір шрифта Висота рядка - Висота абзацу + Висота абзаца Абзацний відступ - Колір фону - Колір шрифту + Колір фона + Колір шрифта Червоний @@ -172,7 +192,6 @@ Обкладинка була успішно змінена. Обкладинка була успішно видалена. Назва була успішно змінена. - Файл \"%1$s\" не знайдено. Ця книга була успішно видалена. Ця книга була успішно переміщена. Скопійовано @@ -180,6 +199,8 @@ Елемент історії був успішно видалений. + Файл \"%1$s\" не знайдено. + Текст, автор та опис книги були успішно оновлені. Перемістити @@ -200,6 +221,7 @@ Дякуємо за те, що прочитали \"%1$s\" з нами. Ми сподіваємось, що вам сподобалася ця книга! + %1$s букв, %2$s слів. За замовчуванням @@ -208,4 +230,11 @@ Сьогодні Вчора + + Оновити текст, автора та опис книги + Продовжити читання + Назад + Обкладинка + Відкрити налаштування читача + \ 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 d4206141..5d8568b1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - 0.8.0 + 0.9.0 Book\'s Story @@ -57,11 +57,20 @@ No description. - File was not found. Please check whether this book\'s file still exist. + File was not found. Please check whether this book\'s file still exists. Something went wrong. Please check whether this book\'s file is not corrupted. + Wrong file format. + Error: %1$s + This file is encrypted. + This file is empty or corrupted. + No translator app found. + No browser app found. + + The book content is empty. Please, make sure that the book content is not empty and update it. + @@ -96,6 +105,10 @@ Move this book to \"Already Read\" Back to Library Undo + Copy + Translate + Dictionary + Retry files @@ -106,6 +119,7 @@ Unknown You need to read more! Keep it up! + Well done, you have read the whole book! Change cover image Choose new cover from your photos Delete cover image @@ -123,9 +137,15 @@ General App language Appearance - App theme, dark theme - Font - Text + Theme preferences, colors + Reader + Font settings, text settings + + + Font + Text + Theme preferences + Colors App language @@ -172,7 +192,6 @@ Cover Image was successfully changed. Cover Image was successfully deleted. Title was successfully changed. - File \"%1$s\" was not found. This book was successfully deleted. This book was successfully moved. Copied @@ -180,6 +199,8 @@ History element was successfully deleted. + File \"%1$s\" was not found. + Book text, author and description were successfully updated. Move @@ -200,6 +221,7 @@ Thank you for reading \"%1$s\" with us. We hope you enjoyed this book! + %1$s characters, %2$s words. Default @@ -208,4 +230,11 @@ Today Yesterday + + Refresh text, author and description of the book + Continue reading + Go back + Cover image + Open reader settings + \ 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 972f848f..ad932758 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -3,6 +3,7 @@