From 726626f2c5bf99f0d35abd88ab51db778324d9dc Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Sat, 3 Dec 2022 10:35:30 +0700 Subject: [PATCH] Merge Voyager screens (#8656) * Merge Voyager screens * cleanups (cherry picked from commit 3d66eaea8373b6ab5d8e8423be227e0452cb0743) # Conflicts: # app/src/main/java/eu/kanade/presentation/components/MangaBottomActionMenu.kt # app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/base/changehandler/OneWayFadeChangeHandler.kt # app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesFilterController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt # app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt # app/src/main/java/eu/kanade/tachiyomi/ui/main/WhatsNewDialogController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt # app/src/main/res/layout/main_activity.xml --- app/build.gradle.kts | 9 +- .../components/MangaBottomActionMenu.kt | 58 +- .../presentation/components/NavigationBar.kt | 48 ++ .../presentation/components/NavigationRail.kt | 59 ++ .../presentation/components/Scaffold.kt | 10 +- .../presentation/components/TabbedScreen.kt | 5 +- .../presentation/history/HistoryScreen.kt | 3 - .../eu/kanade/presentation/more/MoreScreen.kt | 15 +- .../presentation/more/NewUpdateScreen.kt | 144 ++++ .../more/settings/screen/AboutScreen.kt | 36 +- .../settings/screen/ConfigureExhDialog.kt | 121 ++++ .../settings/screen/SettingsAdvancedScreen.kt | 8 +- .../settings/screen/SettingsBrowseScreen.kt | 15 +- .../more/settings/screen/SettingsEhScreen.kt | 11 +- .../settings/screen/SettingsSecurityScreen.kt | 9 +- .../more/settings/screen/WhatsNewDialog.kt | 156 +++++ .../presentation/updates/UpdatesScreen.kt | 3 - .../eu/kanade/presentation/util/Navigator.kt | 17 +- .../data/notification/NotificationReceiver.kt | 4 +- .../glance/UpdatesGridGlanceWidget.kt | 4 +- .../changehandler/OneWayFadeChangeHandler.kt | 41 -- .../ui/base/controller/BaseController.kt | 86 --- .../ui/base/controller/ComposeController.kt | 49 -- .../ui/base/controller/ConductorExtensions.kt | 26 - .../ui/base/controller/DialogController.kt | 119 ---- .../ui/base/controller/RootController.kt | 3 - .../tachiyomi/ui/browse/BrowseController.kt | 27 - .../browse/{BrowseScreen.kt => BrowseTab.kt} | 26 +- .../tachiyomi/ui/browse/feed/FeedTab.kt | 23 +- .../advanced/design/PreMigrationController.kt | 43 -- .../advanced/design/PreMigrationScreen.kt | 9 +- .../process/MigrationListController.kt | 30 - .../advanced/process/MigrationListScreen.kt | 83 +-- .../search/SourceSearchController.kt | 34 - .../migration/search/SourceSearchScreen.kt | 17 +- .../sources/MigrationSourcesController.kt | 15 - .../browse/source/SourcesFilterController.kt | 17 - .../ui/browse/source/SourcesFilterScreen.kt | 8 +- .../tachiyomi/ui/browse/source/SourcesTab.kt | 27 +- .../source/browse/BrowseSourceController.kt | 147 ----- .../source/browse/BrowseSourceScreen.kt | 35 +- .../source/browse/BrowseSourceScreenModel.kt | 6 +- .../browse/source/browse/SourceFilterSheet.kt | 12 +- .../source/feed/SourceFeedController.kt | 37 -- .../ui/browse/source/feed/SourceFeedScreen.kt | 52 +- .../source/feed/SourceFeedScreenModel.kt | 6 - .../globalsearch/GlobalSearchController.kt | 25 - .../source/globalsearch/GlobalSearchScreen.kt | 17 +- .../ui/category/CategoryController.kt | 17 - .../tachiyomi/ui/category/CategoryScreen.kt | 9 +- .../biometric/BiometricTimesController.kt | 20 - .../biometric/BiometricTimesScreen.kt | 6 +- .../biometric/BiometricTimesScreenModel.kt | 3 - .../ui/category/genre/SortTagController.kt | 20 - .../ui/category/genre/SortTagScreen.kt | 6 +- .../ui/category/genre/SortTagScreenModel.kt | 3 - .../ui/category/repos/RepoController.kt | 20 - .../tachiyomi/ui/category/repos/RepoScreen.kt | 6 +- .../ui/category/repos/RepoScreenModel.kt | 3 - .../sources/SourceCategoryController.kt | 20 - .../category/sources/SourceCategoryScreen.kt | 6 +- .../sources/SourceCategoryScreenModel.kt | 3 - .../ui/download/DownloadController.kt | 15 - .../ui/download/DownloadQueueScreen.kt | 6 +- .../tachiyomi/ui/history/HistoryController.kt | 26 - .../ui/history/HistoryScreenModel.kt | 5 + .../{HistoryScreen.kt => HistoryTab.kt} | 62 +- .../eu/kanade/tachiyomi/ui/home/HomeScreen.kt | 306 +++++++++ .../tachiyomi/ui/library/LibraryController.kt | 53 -- .../ui/library/LibrarySettingsSheet.kt | 14 +- .../{LibraryScreen.kt => LibraryTab.kt} | 196 +++--- .../kanade/tachiyomi/ui/main/MainActivity.kt | 616 +++++------------- .../ui/main/WhatsNewDialogController.kt | 34 - .../tachiyomi/ui/manga/MangaController.kt | 53 -- .../kanade/tachiyomi/ui/manga/MangaScreen.kt | 121 ++-- .../tachiyomi/ui/more/MoreController.kt | 18 - .../ui/more/{MoreScreen.kt => MoreTab.kt} | 66 +- .../ui/more/NewUpdateDialogController.kt | 62 -- .../tachiyomi/ui/more/NewUpdateScreen.kt | 41 ++ .../tachiyomi/ui/reader/ReaderActivity.kt | 4 +- .../ui/setting/SettingsMainController.kt | 37 -- .../tachiyomi/ui/setting/SettingsScreen.kt | 12 +- .../tachiyomi/ui/stats/StatsController.kt | 13 - .../kanade/tachiyomi/ui/stats/StatsScreen.kt | 8 +- .../tachiyomi/ui/updates/UpdatesController.kt | 13 - .../{UpdatesScreen.kt => UpdatesTab.kt} | 58 +- .../eu/kanade/tachiyomi/util/Constants.kt | 7 + .../widget/TachiyomiBottomNavigationView.kt | 196 ------ .../TachiyomiChangeHandlerFrameLayout.kt | 53 -- ...ugController.kt => SettingsDebugScreen.kt} | 27 +- .../java/exh/md/MangaDexFabHeaderAdapter.kt | 19 +- .../md/follows/MangaDexFollowsController.kt | 30 - .../exh/md/follows/MangaDexFollowsScreen.kt | 25 +- .../md/follows/MangaDexFollowsScreenModel.kt | 4 +- .../md/similar/MangaDexSimilarController.kt | 34 - .../exh/md/similar/MangaDexSimilarScreen.kt | 16 +- .../md/similar/MangaDexSimilarScreenModel.kt | 7 +- .../exh/pagepreview/PagePreviewController.kt | 26 - .../java/exh/pagepreview/PagePreviewScreen.kt | 6 +- .../java/exh/recs/RecommendsController.kt | 34 - .../main/java/exh/recs/RecommendsScreen.kt | 11 +- .../java/exh/recs/RecommendsScreenModel.kt | 3 - .../uconfig/ConfiguringDialogController.kt | 75 --- .../uconfig/WarnConfigureDialogController.kt | 37 -- .../exh/ui/batchadd/BatchAddController.kt | 16 - .../java/exh/ui/batchadd/BatchAddScreen.kt | 9 +- .../exh/ui/intercept/InterceptActivity.kt | 4 +- .../exh/ui/metadata/MetadataViewController.kt | 30 - .../ui/smartsearch/SmartSearchController.kt | 31 - .../exh/ui/smartsearch/SmartSearchScreen.kt | 27 +- app/src/main/res/drawable/anim_more_enter.xml | 4 +- .../main/res/layout-sw720dp/main_activity.xml | 80 --- .../res/layout/changelog_header_layout.xml | 25 - .../main/res/layout/changelog_row_layout.xml | 35 - app/src/main/res/layout/main_activity.xml | 68 -- app/src/main/res/raw/changelog_release.xml | 2 +- gradle/libs.versions.toml | 12 +- gradle/sy.versions.toml | 1 - i18n/src/main/res/values/strings.xml | 1 + i18n/src/main/res/values/strings_sy.xml | 3 + 120 files changed, 1685 insertions(+), 3009 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/components/NavigationBar.kt create mode 100644 app/src/main/java/eu/kanade/presentation/components/NavigationRail.kt create mode 100644 app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt create mode 100644 app/src/main/java/eu/kanade/presentation/more/settings/screen/ConfigureExhDialog.kt create mode 100644 app/src/main/java/eu/kanade/presentation/more/settings/screen/WhatsNewDialog.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/base/changehandler/OneWayFadeChangeHandler.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ComposeController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/DialogController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/RootController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseController.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/browse/{BrowseScreen.kt => BrowseTab.kt} (75%) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesController.kt delete mode 100755 app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesFilterController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/category/biometric/BiometricTimesController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/category/repos/RepoController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryController.kt delete mode 100755 app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryController.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/history/{HistoryScreen.kt => HistoryTab.kt} (62%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/library/{LibraryScreen.kt => LibraryTab.kt} (67%) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/main/WhatsNewDialogController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/more/{MoreScreen.kt => MoreTab.kt} (63%) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateDialogController.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreen.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesController.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/updates/{UpdatesScreen.kt => UpdatesTab.kt} (62%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/Constants.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiChangeHandlerFrameLayout.kt rename app/src/main/java/exh/debug/{SettingsDebugController.kt => SettingsDebugScreen.kt} (94%) delete mode 100644 app/src/main/java/exh/md/follows/MangaDexFollowsController.kt delete mode 100644 app/src/main/java/exh/md/similar/MangaDexSimilarController.kt delete mode 100644 app/src/main/java/exh/pagepreview/PagePreviewController.kt delete mode 100644 app/src/main/java/exh/recs/RecommendsController.kt delete mode 100644 app/src/main/java/exh/uconfig/ConfiguringDialogController.kt delete mode 100644 app/src/main/java/exh/uconfig/WarnConfigureDialogController.kt delete mode 100755 app/src/main/java/exh/ui/batchadd/BatchAddController.kt delete mode 100644 app/src/main/java/exh/ui/metadata/MetadataViewController.kt delete mode 100644 app/src/main/java/exh/ui/smartsearch/SmartSearchController.kt delete mode 100644 app/src/main/res/layout-sw720dp/main_activity.xml delete mode 100755 app/src/main/res/layout/changelog_header_layout.xml delete mode 100755 app/src/main/res/layout/changelog_row_layout.xml delete mode 100755 app/src/main/res/layout/main_activity.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f03d9cb97..515230dd2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -264,15 +264,12 @@ dependencies { exclude(group = "androidx.viewpager", module = "viewpager") } implementation(libs.insetter) - implementation(libs.markwon) + implementation(libs.bundles.richtext) implementation(libs.aboutLibraries.compose) implementation(libs.cascade) implementation(libs.bundles.voyager) implementation(libs.wheelpicker) - // Conductor - implementation(libs.conductor) - // FlowBinding implementation(libs.flowbinding.android) @@ -294,9 +291,6 @@ dependencies { implementation(libs.leakcanary.plumber) // SY --> - // Changelog - implementation(sylibs.changelog) - // Text distance (EH) implementation (sylibs.simularity) @@ -350,6 +344,7 @@ tasks { kotlinOptions.freeCompilerArgs += listOf( "-opt-in=coil.annotation.ExperimentalCoilApi", "-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi", + "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", "-opt-in=androidx.compose.material.ExperimentalMaterialApi", "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", diff --git a/app/src/main/java/eu/kanade/presentation/components/MangaBottomActionMenu.kt b/app/src/main/java/eu/kanade/presentation/components/MangaBottomActionMenu.kt index 45d8cb708..beb3be4b4 100644 --- a/app/src/main/java/eu/kanade/presentation/components/MangaBottomActionMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/components/MangaBottomActionMenu.kt @@ -2,6 +2,7 @@ package eu.kanade.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -16,10 +17,10 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.ZeroCornerSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.BookmarkAdd @@ -100,7 +101,11 @@ fun MangaBottomActionMenu( } Row( modifier = Modifier - .padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()) + .padding( + WindowInsets.navigationBars + .only(WindowInsetsSides.Bottom) + .asPaddingValues(), + ) .padding(horizontal = 8.dp, vertical = 12.dp), ) { if (onBookmarkClicked != null) { @@ -218,11 +223,11 @@ private fun RowScope.Button( fun LibraryBottomActionMenu( visible: Boolean, modifier: Modifier = Modifier, - onChangeCategoryClicked: (() -> Unit)?, - onMarkAsReadClicked: (() -> Unit)?, - onMarkAsUnreadClicked: (() -> Unit)?, + onChangeCategoryClicked: () -> Unit, + onMarkAsReadClicked: () -> Unit, + onMarkAsUnreadClicked: () -> Unit, onDownloadClicked: ((DownloadAction) -> Unit)?, - onDeleteClicked: (() -> Unit)?, + onDeleteClicked: () -> Unit, // SY --> onClickCleanTitles: (() -> Unit)?, onClickMigrate: (() -> Unit)?, @@ -231,8 +236,8 @@ fun LibraryBottomActionMenu( ) { AnimatedVisibility( visible = visible, - enter = expandVertically(expandFrom = Alignment.Bottom), - exit = shrinkVertically(shrinkTowards = Alignment.Bottom), + enter = expandVertically(animationSpec = tween(delayMillis = 300)), + exit = shrinkVertically(animationSpec = tween()), ) { val scope = rememberCoroutineScope() Surface( @@ -260,18 +265,19 @@ fun LibraryBottomActionMenu( // SY <-- Row( modifier = Modifier - .navigationBarsPadding() + .windowInsetsPadding( + WindowInsets.navigationBars + .only(WindowInsetsSides.Bottom), + ) .padding(horizontal = 8.dp, vertical = 12.dp), ) { - if (onChangeCategoryClicked != null) { - Button( - title = stringResource(R.string.action_move_category), - icon = Icons.Outlined.Label, - toConfirm = confirm[0], - onLongClick = { onLongClickItem(0) }, - onClick = onChangeCategoryClicked, - ) - } + Button( + title = stringResource(R.string.action_move_category), + icon = Icons.Outlined.Label, + toConfirm = confirm[0], + onLongClick = { onLongClickItem(0) }, + onClick = onChangeCategoryClicked, + ) if (onDownloadClicked != null) { var downloadExpanded by remember { mutableStateOf(false) } Button( @@ -290,15 +296,13 @@ fun LibraryBottomActionMenu( ) } } - if (onDeleteClicked != null) { - Button( - title = stringResource(R.string.action_delete), - icon = Icons.Outlined.Delete, - toConfirm = confirm[4], - onLongClick = { onLongClickItem(4) }, - onClick = onDeleteClicked, - ) - } + Button( + title = stringResource(R.string.action_delete), + icon = Icons.Outlined.Delete, + toConfirm = confirm[4], + onLongClick = { onLongClickItem(4) }, + onClick = onDeleteClicked, + ) // SY --> if (onMarkAsReadClicked != null) { Button( diff --git a/app/src/main/java/eu/kanade/presentation/components/NavigationBar.kt b/app/src/main/java/eu/kanade/presentation/components/NavigationBar.kt new file mode 100644 index 000000000..9c4143f80 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/components/NavigationBar.kt @@ -0,0 +1,48 @@ +package eu.kanade.presentation.components + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.selection.selectableGroup +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBarDefaults +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * M3 Navbar with no horizontal spacer + * + * @see [androidx.compose.material3.NavigationBar] + */ +@Composable +fun NavigationBar( + modifier: Modifier = Modifier, + containerColor: Color = NavigationBarDefaults.containerColor, + contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor), + tonalElevation: Dp = NavigationBarDefaults.Elevation, + windowInsets: WindowInsets = NavigationBarDefaults.windowInsets, + content: @Composable RowScope.() -> Unit, +) { + androidx.compose.material3.Surface( + color = containerColor, + contentColor = contentColor, + tonalElevation = tonalElevation, + modifier = modifier, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .windowInsetsPadding(windowInsets) + .height(80.dp) + .selectableGroup(), + content = content, + ) + } +} diff --git a/app/src/main/java/eu/kanade/presentation/components/NavigationRail.kt b/app/src/main/java/eu/kanade/presentation/components/NavigationRail.kt new file mode 100644 index 000000000..4a0778b07 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/components/NavigationRail.kt @@ -0,0 +1,59 @@ +package eu.kanade.presentation.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.selection.selectableGroup +import androidx.compose.material3.NavigationRailDefaults +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +/** + * Center-aligned M3 Navigation rail + * + * @see [androidx.compose.material3.NavigationRail] + */ +@Composable +fun NavigationRail( + modifier: Modifier = Modifier, + containerColor: Color = NavigationRailDefaults.ContainerColor, + contentColor: Color = contentColorFor(containerColor), + header: @Composable (ColumnScope.() -> Unit)? = null, + windowInsets: WindowInsets = NavigationRailDefaults.windowInsets, + content: @Composable ColumnScope.() -> Unit, +) { + androidx.compose.material3.Surface( + color = containerColor, + contentColor = contentColor, + modifier = modifier, + tonalElevation = 3.dp, + ) { + Column( + Modifier + .fillMaxHeight() + .windowInsetsPadding(windowInsets) + .widthIn(min = 80.dp) + .padding(vertical = 4.dp) + .selectableGroup(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically), + ) { + if (header != null) { + header() + Spacer(Modifier.height(8.dp)) + } + content() + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/components/Scaffold.kt b/app/src/main/java/eu/kanade/presentation/components/Scaffold.kt index e4e05d0ae..de708bcd5 100644 --- a/app/src/main/java/eu/kanade/presentation/components/Scaffold.kt +++ b/app/src/main/java/eu/kanade/presentation/components/Scaffold.kt @@ -16,11 +16,14 @@ package eu.kanade.presentation.components +import androidx.compose.foundation.layout.MutableWindowInsets import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.exclude +import androidx.compose.foundation.layout.withConsumedWindowInsets import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ScaffoldDefaults @@ -31,6 +34,7 @@ import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -67,6 +71,7 @@ import kotlin.math.max * * Remove height constraint for expanded app bar * * Also take account of fab height when providing inner padding * * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used + * * Handle consumed window insets * * @param modifier the [Modifier] to be applied to this scaffold * @param topBar top app bar of the screen, typically a [SmallTopAppBar] @@ -103,9 +108,12 @@ fun Scaffold( contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, content: @Composable (PaddingValues) -> Unit, ) { + // Tachiyomi: Handle consumed window insets + val remainingWindowInsets = remember { MutableWindowInsets() } androidx.compose.material3.Surface( modifier = Modifier .nestedScroll(topBarScrollBehavior.nestedScrollConnection) + .withConsumedWindowInsets { remainingWindowInsets.insets = contentWindowInsets.exclude(it) } .then(modifier), color = containerColor, contentColor = contentColor, @@ -116,7 +124,7 @@ fun Scaffold( bottomBar = bottomBar, content = content, snackbar = snackbarHost, - contentWindowInsets = contentWindowInsets, + contentWindowInsets = remainingWindowInsets, fab = floatingActionButton, ) } diff --git a/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt b/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt index 3eb46c15a..fe03d10a5 100644 --- a/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource -import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView import kotlinx.coroutines.launch @Composable @@ -88,9 +87,7 @@ fun TabbedScreen( verticalAlignment = Alignment.Top, ) { page -> tabs[page].content( - TachiyomiBottomNavigationView.withBottomNavPadding( - PaddingValues(bottom = contentPadding.calculateBottomPadding()), - ), + PaddingValues(bottom = contentPadding.calculateBottomPadding()), snackbarHostState, ) } diff --git a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt index 5157e6f08..17ac024c9 100644 --- a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.DeleteSweep import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable @@ -21,7 +20,6 @@ import eu.kanade.presentation.history.components.HistoryContent import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.history.HistoryScreenModel import eu.kanade.tachiyomi.ui.history.HistoryState -import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView import java.util.Date @Composable @@ -55,7 +53,6 @@ fun HistoryScreen( ) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, - contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets), ) { contentPadding -> state.list.let { if (it == null) { diff --git a/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt b/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt index 1b7519a0f..268920ef1 100644 --- a/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt @@ -1,10 +1,7 @@ package eu.kanade.presentation.more import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CloudOff import androidx.compose.material.icons.outlined.GetApp @@ -32,8 +29,7 @@ import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.more.DownloadQueueState -import eu.kanade.tachiyomi.ui.more.MoreController -import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView +import eu.kanade.tachiyomi.util.Constants @Composable fun MoreScreen( @@ -60,10 +56,7 @@ fun MoreScreen( val uriHandler = LocalUriHandler.current ScrollbarLazyColumn( - modifier = Modifier.statusBarsPadding(), - contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding( - WindowInsets.navigationBars.asPaddingValues(), - ), + modifier = Modifier.systemBarsPadding(), ) { if (isFDroid) { item { @@ -209,7 +202,7 @@ fun MoreScreen( TextPreferenceWidget( title = stringResource(R.string.label_help), icon = Icons.Outlined.HelpOutline, - onPreferenceClick = { uriHandler.openUri(MoreController.URL_HELP) }, + onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) }, ) } } diff --git a/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt b/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt new file mode 100644 index 000000000..0e881a161 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt @@ -0,0 +1,144 @@ +package eu.kanade.presentation.more + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.OpenInNew +import androidx.compose.material.icons.outlined.NewReleases +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBarDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import com.halilibo.richtext.markdown.Markdown +import com.halilibo.richtext.ui.RichTextStyle +import com.halilibo.richtext.ui.material3.Material3RichText +import com.halilibo.richtext.ui.string.RichTextStringStyle +import eu.kanade.presentation.components.Scaffold +import eu.kanade.presentation.util.padding +import eu.kanade.presentation.util.secondaryItemAlpha +import eu.kanade.tachiyomi.R + +@Composable +fun NewUpdateScreen( + versionName: String, + changelogInfo: String, + onOpenInBrowser: () -> Unit, + onRejectUpdate: () -> Unit, + onAcceptUpdate: () -> Unit, +) { + Scaffold( + bottomBar = { + val strokeWidth = Dp.Hairline + val borderColor = MaterialTheme.colorScheme.outline + Column( + modifier = Modifier + .background(MaterialTheme.colorScheme.background) + .drawBehind { + drawLine( + borderColor, + Offset(0f, 0f), + Offset(size.width, 0f), + strokeWidth.value, + ) + } + .windowInsetsPadding(NavigationBarDefaults.windowInsets) + .padding( + horizontal = MaterialTheme.padding.medium, + vertical = MaterialTheme.padding.small, + ), + ) { + Button( + modifier = Modifier.fillMaxWidth(), + onClick = onAcceptUpdate, + ) { + Text(text = stringResource(id = R.string.update_check_confirm)) + } + TextButton( + modifier = Modifier.fillMaxWidth(), + onClick = onRejectUpdate, + ) { + Text(text = stringResource(R.string.action_not_now)) + } + } + }, + ) { paddingValues -> + // Status bar scrim + Box( + modifier = Modifier + .zIndex(2f) + .secondaryItemAlpha() + .background(MaterialTheme.colorScheme.background) + .fillMaxWidth() + .height(paddingValues.calculateTopPadding()), + ) + + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(paddingValues) + .padding(top = 48.dp) + .padding(horizontal = MaterialTheme.padding.medium), + ) { + Icon( + imageVector = Icons.Outlined.NewReleases, + contentDescription = null, + modifier = Modifier + .padding(bottom = MaterialTheme.padding.small) + .size(48.dp), + tint = MaterialTheme.colorScheme.primary, + ) + Text( + text = stringResource(R.string.update_check_notification_update_available), + style = MaterialTheme.typography.headlineLarge, + ) + Text( + text = versionName, + modifier = Modifier.secondaryItemAlpha(), + style = MaterialTheme.typography.titleSmall, + ) + + Material3RichText( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = MaterialTheme.padding.large), + style = RichTextStyle( + stringStyle = RichTextStringStyle( + linkStyle = SpanStyle(color = MaterialTheme.colorScheme.primary), + ), + ), + ) { + Markdown(content = changelogInfo) + + TextButton( + onClick = onOpenInBrowser, + modifier = Modifier.padding(top = MaterialTheme.padding.small), + ) { + Text(text = stringResource(R.string.update_check_open)) + Spacer(modifier = Modifier.width(MaterialTheme.padding.tiny)) + Icon(imageVector = Icons.Default.OpenInNew, contentDescription = null) + } + } + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt index 2c2d74765..180548e1c 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt @@ -8,7 +8,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Public import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.LocalContext @@ -19,7 +23,6 @@ import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import com.bluelinelabs.conductor.Router import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.LinkIcon @@ -29,13 +32,11 @@ import eu.kanade.presentation.more.LogoHeader import eu.kanade.presentation.more.about.LicensesScreen import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.presentation.util.LocalBackPress -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.updater.AppUpdateChecker import eu.kanade.tachiyomi.data.updater.AppUpdateResult -import eu.kanade.tachiyomi.ui.main.WhatsNewDialogController -import eu.kanade.tachiyomi.ui.more.NewUpdateDialogController +import eu.kanade.tachiyomi.ui.more.NewUpdateScreen import eu.kanade.tachiyomi.util.CrashLogUtil import eu.kanade.tachiyomi.util.lang.toDateTimestampString import eu.kanade.tachiyomi.util.lang.withIOContext @@ -63,7 +64,10 @@ object AboutScreen : Screen { val uriHandler = LocalUriHandler.current val handleBack = LocalBackPress.current val navigator = LocalNavigator.currentOrThrow - val router = LocalRouter.currentOrThrow + + // SY --> + var showWhatsNewDialog by remember { mutableStateOf(false) } + // SY <-- Scaffold( topBar = { scrollBehavior -> @@ -98,7 +102,15 @@ object AboutScreen : Screen { title = stringResource(R.string.check_for_updates), onPreferenceClick = { scope.launch { - checkVersion(context, router) + checkVersion(context) { result -> + val updateScreen = NewUpdateScreen( + versionName = result.release.version, + changelogInfo = result.release.info, + releaseLink = result.release.releaseLink, + downloadLink = result.release.getDownloadLink(), + ) + navigator.push(updateScreen) + } } }, ) @@ -109,7 +121,7 @@ object AboutScreen : Screen { TextPreferenceWidget( title = stringResource(R.string.whats_new), // SY --> - onPreferenceClick = { WhatsNewDialogController().showDialog(router) }, + onPreferenceClick = { showWhatsNewDialog = true }, // SY <-- ) } @@ -179,19 +191,25 @@ object AboutScreen : Screen { } } } + + // SY --> + if (showWhatsNewDialog) { + WhatsNewDialog(onDismissRequest = { showWhatsNewDialog = false }) + } + // SY <-- } /** * Checks version and shows a user prompt if an update is available. */ - private suspend fun checkVersion(context: Context, router: Router) { + private suspend fun checkVersion(context: Context, onAvailableUpdate: (AppUpdateResult.NewUpdate) -> Unit) { val updateChecker = AppUpdateChecker() withUIContext { context.toast(R.string.update_check_look_for_updates) try { when (val result = withIOContext { updateChecker.checkForUpdate(context, isUserPrompt = true) }) { is AppUpdateResult.NewUpdate -> { - NewUpdateDialogController(result).showDialog(router) + onAvailableUpdate(result) } is AppUpdateResult.NoNewUpdate -> { context.toast(R.string.update_check_no_new_updates) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/ConfigureExhDialog.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/ConfigureExhDialog.kt new file mode 100644 index 000000000..47f69a0e2 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/ConfigureExhDialog.kt @@ -0,0 +1,121 @@ +package eu.kanade.presentation.more.settings.screen + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +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.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.window.DialogProperties +import eu.kanade.domain.UnsortedPreferences +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.lang.launchUI +import eu.kanade.tachiyomi.util.system.toast +import exh.log.xLogE +import exh.uconfig.EHConfigurator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.withContext +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +@Composable +fun ConfigureExhDialog(run: Boolean, onRunning: () -> Unit) { + val unsortedPreferences = remember { + Injekt.get() + } + var warnDialogOpen by remember { mutableStateOf(false) } + var configureDialogOpen by remember { mutableStateOf(false) } + var configureFailedDialogOpen by remember { mutableStateOf(null) } + + LaunchedEffect(run) { + if (run) { + if (unsortedPreferences.exhShowSettingsUploadWarning().get()) { + warnDialogOpen = true + } else { + configureDialogOpen = true + } + onRunning() + } + } + + if (warnDialogOpen) { + AlertDialog( + onDismissRequest = { warnDialogOpen = false }, + properties = DialogProperties( + dismissOnBackPress = false, + dismissOnClickOutside = false, + ), + confirmButton = { + TextButton( + onClick = { + unsortedPreferences.exhShowSettingsUploadWarning().set(false) + configureDialogOpen = true + warnDialogOpen = false + }, + ) { + Text(text = stringResource(android.R.string.ok)) + } + }, + title = { + Text(text = stringResource(R.string.settings_profile_note)) + }, + text = { + Text(text = stringResource(R.string.settings_profile_note_message)) + }, + ) + } + if (configureDialogOpen) { + val context = LocalContext.current + LaunchedEffect(Unit) { + withContext(Dispatchers.IO + NonCancellable) { + try { + EHConfigurator(context).configureAll() + launchUI { + context.toast(R.string.eh_settings_successfully_uploaded) + } + } catch (e: Exception) { + configureFailedDialogOpen = e + xLogE("Configuration error!", e) + } finally { + configureDialogOpen = false + } + } + } + AlertDialog( + onDismissRequest = {}, + properties = DialogProperties( + dismissOnBackPress = false, + dismissOnClickOutside = false, + ), + confirmButton = {}, + title = { + Text(text = stringResource(R.string.eh_settings_uploading_to_server)) + }, + text = { + Text(text = stringResource(R.string.eh_settings_uploading_to_server_message)) + }, + ) + } + if (configureFailedDialogOpen != null) { + AlertDialog( + onDismissRequest = { configureFailedDialogOpen = null }, + confirmButton = { + TextButton(onClick = { configureFailedDialogOpen = null }) { + Text(text = stringResource(android.R.string.ok)) + } + }, + title = { + Text(text = stringResource(R.string.eh_settings_configuration_failed)) + }, + text = { + Text(text = stringResource(R.string.eh_settings_configuration_failed_message, configureFailedDialogOpen?.message.orEmpty())) + }, + ) + } +} diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt index c9ce37649..c495c97df 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt @@ -46,7 +46,6 @@ import eu.kanade.domain.manga.interactor.GetAllManga import eu.kanade.domain.manga.repository.MangaRepository import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.more.settings.Preference -import eu.kanade.presentation.util.LocalRouter import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.ChapterCache @@ -71,7 +70,6 @@ import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101 import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9 import eu.kanade.tachiyomi.network.PREF_DOH_SHECAN import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.ui.base.controller.pushController import eu.kanade.tachiyomi.util.CrashLogUtil import eu.kanade.tachiyomi.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.withUIContext @@ -82,7 +80,7 @@ import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.powerManager import eu.kanade.tachiyomi.util.system.setDefaultSettings import eu.kanade.tachiyomi.util.system.toast -import exh.debug.SettingsDebugController +import exh.debug.SettingsDebugScreen import exh.log.EHLogLevel import exh.pref.DelegateSourcePreferences import exh.source.BlacklistedSources @@ -673,7 +671,7 @@ object SettingsAdvancedScreen : SearchableSettings { @Composable private fun getDeveloperToolsGroup(): Preference.PreferenceGroup { val context = LocalContext.current - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val sourcePreferences = remember { Injekt.get() } val unsortedPreferences = remember { Injekt.get() } val delegateSourcePreferences = remember { Injekt.get() } @@ -729,7 +727,7 @@ object SettingsAdvancedScreen : SearchableSettings { HtmlCompat.fromHtml(context.getString(R.string.open_debug_menu_summary), HtmlCompat.FROM_HTML_MODE_COMPACT) .toAnnotatedString() }, - onClick = { router.pushController(SettingsDebugController()) }, + onClick = { navigator.push(SettingsDebugScreen()) }, ), ), ) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt index 61697a7b0..c2b8449b3 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt @@ -9,19 +9,18 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.fragment.app.FragmentActivity +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.domain.UnsortedPreferences import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.more.settings.Preference -import eu.kanade.presentation.util.LocalRouter import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.extension.ExtensionUpdateJob -import eu.kanade.tachiyomi.ui.base.controller.pushController -import eu.kanade.tachiyomi.ui.category.repos.RepoController -import eu.kanade.tachiyomi.ui.category.sources.SourceCategoryController +import eu.kanade.tachiyomi.ui.category.repos.RepoScreen +import eu.kanade.tachiyomi.ui.category.sources.SourceCategoryScreen import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -48,13 +47,13 @@ object SettingsBrowseScreen : SearchableSettings { title = stringResource(R.string.label_sources), preferenceItems = listOf( kotlin.run { - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val count by sourcePreferences.sourcesTabCategories().collectAsState() Preference.PreferenceItem.TextPreference( title = stringResource(R.string.action_edit_categories), subtitle = pluralStringResource(R.plurals.num_categories, count.size, count.size), onClick = { - router.pushController(SourceCategoryController()) + navigator.push(SourceCategoryScreen()) }, ) }, @@ -99,13 +98,13 @@ object SettingsBrowseScreen : SearchableSettings { ), // SY --> kotlin.run { - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val count by unsortedPreferences.extensionRepos().collectAsState() Preference.PreferenceItem.TextPreference( title = stringResource(R.string.action_edit_repos), subtitle = pluralStringResource(R.plurals.num_repos, count.size, count.size), onClick = { - router.pushController(RepoController()) + navigator.push(RepoScreen()) }, ) }, diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsEhScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsEhScreen.kt index acd99b56b..20132c47a 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsEhScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsEhScreen.kt @@ -46,13 +46,11 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.core.content.ContextCompat.startActivity -import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.domain.UnsortedPreferences import eu.kanade.domain.manga.interactor.DeleteFavoriteEntries import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata import eu.kanade.domain.manga.interactor.GetFlatMetadataById import eu.kanade.presentation.more.settings.Preference -import eu.kanade.presentation.util.LocalRouter import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING @@ -68,7 +66,6 @@ import exh.eh.EHentaiUpdateWorkerConstants import exh.eh.EHentaiUpdaterStats import exh.favorites.FavoritesIntroDialog import exh.metadata.metadata.EHentaiSearchMetadata -import exh.uconfig.WarnConfigureDialogController import exh.ui.login.EhLoginActivity import exh.util.nullIfBlank import kotlinx.serialization.decodeFromString @@ -126,18 +123,18 @@ object SettingsEhScreen : SearchableSettings { @Composable override fun getPreferences(): List { - val router = LocalRouter.currentOrThrow - val openWarnConfigureDialogController = { - WarnConfigureDialogController.uploadSettings(router) - } val unsortedPreferences: UnsortedPreferences = remember { Injekt.get() } val getFlatMetadataById: GetFlatMetadataById = remember { Injekt.get() } val deleteFavoriteEntries: DeleteFavoriteEntries = remember { Injekt.get() } val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata = remember { Injekt.get() } val exhentaiEnabled by unsortedPreferences.enableExhentai().collectAsState() + var runConfigureDialog by remember { mutableStateOf(false) } + val openWarnConfigureDialogController = { runConfigureDialog = true } Reconfigure(unsortedPreferences, openWarnConfigureDialogController) + ConfigureExhDialog(run = runConfigureDialog, onRunning = { runConfigureDialog = false }) + return listOf( Preference.PreferenceGroup( stringResource(R.string.ehentai_prefs_account_settings), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt index 5b06f3b97..2c9586b67 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt @@ -26,15 +26,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties import androidx.fragment.app.FragmentActivity +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.more.settings.Preference -import eu.kanade.presentation.util.LocalRouter import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.core.security.SecurityPreferences -import eu.kanade.tachiyomi.ui.base.controller.pushController import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate -import eu.kanade.tachiyomi.ui.category.biometric.BiometricTimesController +import eu.kanade.tachiyomi.ui.category.biometric.BiometricTimesScreen import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported import uy.kohesive.injekt.Injekt @@ -98,7 +97,7 @@ object SettingsSecurityScreen : SearchableSettings { ), // SY --> kotlin.run { - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val count by securityPreferences.authenticatorTimeRanges().collectAsState() Preference.PreferenceItem.TextPreference( title = stringResource(R.string.action_edit_biometric_lock_times), @@ -108,7 +107,7 @@ object SettingsSecurityScreen : SearchableSettings { count.size, ), onClick = { - router.pushController(BiometricTimesController()) + navigator.push(BiometricTimesScreen()) }, enabled = useAuth, ) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/WhatsNewDialog.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/WhatsNewDialog.kt new file mode 100644 index 000000000..4aec48baf --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/WhatsNewDialog.kt @@ -0,0 +1,156 @@ +package eu.kanade.presentation.more.settings.screen + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import eu.kanade.presentation.components.Divider +import eu.kanade.presentation.components.LazyColumn +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.lang.withIOContext +import eu.kanade.tachiyomi.util.system.isPreviewBuildType +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.AndroidXmlReader +import nl.adaptivity.xmlutil.serialization.XML +import nl.adaptivity.xmlutil.serialization.XmlSerialName +import nl.adaptivity.xmlutil.serialization.XmlValue + +@Composable +fun WhatsNewDialog(onDismissRequest: () -> Unit) { + AlertDialog( + onDismissRequest = onDismissRequest, + confirmButton = { + TextButton(onClick = onDismissRequest) { + Text(text = stringResource(android.R.string.cancel)) + } + }, + title = { + Text(text = stringResource(R.string.whats_new)) + }, + text = { + Column { + val context = LocalContext.current + val changelog by produceState?>(initialValue = null) { + value = withIOContext { + XML.decodeFromReader( + AndroidXmlReader( + context.resources.openRawResource( + if (isPreviewBuildType) R.raw.changelog_debug else R.raw.changelog_release, + ).bufferedReader(), + ), + ).toDisplayChangelog() + } + } + if (changelog != null) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.fillMaxSize(), + ) { + items(changelog.orEmpty()) { changelog -> + Column(Modifier.fillMaxWidth()) { + Text( + text = stringResource(R.string.changelog_version, changelog.version), + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.titleSmall, + ) + Divider(Modifier.padding(vertical = 8.dp)) + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + changelog.changelog.forEach { + Text(text = it, style = MaterialTheme.typography.bodySmall) + } + } + } + } + } + } + } + }, + modifier = Modifier.fillMaxSize(), + ) +} + +data class DisplayChangelog( + val version: String, + val changelog: List, +) + +@Serializable +@XmlSerialName("changelog", "", "") +data class Changelog( + val bulletedList: Boolean, + val changelogs: List, +) + +@Serializable +@XmlSerialName("changelogversion", "", "") +data class ChangelogVersion( + val versionName: String, + val changeDate: String, + val text: List, +) + +@Serializable +@XmlSerialName("changelogtext", "", "") +data class ChangelogText( + @XmlValue(true) val value: String, +) + +private const val bullet = "\u2022" + +fun Changelog.toDisplayChangelog(): List { + val prefix = if (bulletedList) bullet + "\t\t" else "" + return changelogs.map { version -> + DisplayChangelog( + version = version.versionName, + changelog = version.text.mapIndexed { index, changelogText -> + buildAnnotatedString { + append(prefix) + var inBBCode = false + var isEscape = false + changelogText.value.forEachIndexed { charIndex, c -> + if (!inBBCode && c == '[') { + inBBCode = true + } else if (inBBCode && c == ']') { + inBBCode = false + isEscape = false + } else if (inBBCode && c == '/') { + isEscape = true + } else if (inBBCode && c == 'b') { + if (isEscape) { + try { + pop() + } catch (e: IllegalStateException) { + throw Exception("Exception on ${version.versionName}:$index:$charIndex", e) + } + } else { + pushStyle(SpanStyle(fontWeight = FontWeight.Bold)) + } + } else { + append(c) + } + } + } + }, + ) + } +} diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt index b10dadd6d..f6aa59c00 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt @@ -9,7 +9,6 @@ import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material.icons.outlined.SelectAll import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.TopAppBarScrollBehavior @@ -36,7 +35,6 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.ui.updates.UpdatesItem import eu.kanade.tachiyomi.ui.updates.UpdatesState -import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlin.time.Duration.Companion.seconds @@ -87,7 +85,6 @@ fun UpdateScreen( ) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, - contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets), ) { contentPadding -> when { state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) diff --git a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt index b69dd73c4..f2a091556 100644 --- a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt +++ b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt @@ -1,15 +1,11 @@ package eu.kanade.presentation.util import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf -import com.bluelinelabs.conductor.Router - -/** - * For interop with Conductor - */ -val LocalRouter: ProvidableCompositionLocal = staticCompositionLocalOf { null } +import cafe.adriel.voyager.navigator.Navigator /** * For invoking back press to the parent activity @@ -17,3 +13,12 @@ val LocalRouter: ProvidableCompositionLocal = staticCompositionLocalOf val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null } val LocalNavigatorContentPadding: ProvidableCompositionLocal = compositionLocalOf { PaddingValues() } + +interface Tab : cafe.adriel.voyager.navigator.tab.Tab { + suspend fun onReselect(navigator: Navigator) {} + + // SY --> + @Composable + fun isEnabled(): Boolean = true + // SY <-- +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 46ddfb203..70873a0f1 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -23,8 +23,8 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.updater.AppUpdateService import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.getUriCompat @@ -457,7 +457,7 @@ class NotificationReceiver : BroadcastReceiver() { val newIntent = Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - .putExtra(MangaController.MANGA_EXTRA, manga.id) + .putExtra(Constants.MANGA_EXTRA, manga.id) .putExtra("notificationId", manga.id.hashCode()) .putExtra("groupId", groupId) return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) diff --git a/app/src/main/java/eu/kanade/tachiyomi/glance/UpdatesGridGlanceWidget.kt b/app/src/main/java/eu/kanade/tachiyomi/glance/UpdatesGridGlanceWidget.kt index 521f037dd..095834151 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/glance/UpdatesGridGlanceWidget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/glance/UpdatesGridGlanceWidget.kt @@ -48,7 +48,7 @@ import eu.kanade.domain.manga.model.MangaCover import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.core.security.SecurityPreferences import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.system.dpToPx import kotlinx.coroutines.MainScope @@ -136,7 +136,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() { ) { val intent = Intent(LocalContext.current, MainActivity::class.java).apply { action = MainActivity.SHORTCUT_MANGA - putExtra(MangaController.MANGA_EXTRA, mangaId) + putExtra(Constants.MANGA_EXTRA, mangaId) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/changehandler/OneWayFadeChangeHandler.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/changehandler/OneWayFadeChangeHandler.kt deleted file mode 100644 index ba8771c2b..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/changehandler/OneWayFadeChangeHandler.kt +++ /dev/null @@ -1,41 +0,0 @@ -package eu.kanade.tachiyomi.ui.base.changehandler - -import android.animation.Animator -import android.animation.AnimatorSet -import android.animation.ObjectAnimator -import android.view.View -import android.view.ViewGroup -import com.bluelinelabs.conductor.ControllerChangeHandler -import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler -import com.bluelinelabs.conductor.changehandler.FadeChangeHandler - -/** - * An [AnimatorChangeHandler] that will remove the from view and fade in the to view - */ -class OneWayFadeChangeHandler : FadeChangeHandler { - constructor() - constructor(removesFromViewOnPush: Boolean) : super(removesFromViewOnPush) - constructor(duration: Long) : super(duration) - constructor(duration: Long, removesFromViewOnPush: Boolean) : super( - duration, - removesFromViewOnPush, - ) - - override fun getAnimator(container: ViewGroup, from: View?, to: View?, isPush: Boolean, toAddedToContainer: Boolean): Animator { - val animator = AnimatorSet() - if (to != null) { - val start: Float = if (toAddedToContainer) 0F else to.alpha - animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, start, 1f)) - } - - if (from != null && (!isPush || removesFromViewOnPush())) { - from.alpha = 0f - } - - return animator - } - - override fun copy(): ControllerChangeHandler { - return OneWayFadeChangeHandler(animationDuration, removesFromViewOnPush()) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt deleted file mode 100644 index 207f7fed8..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt +++ /dev/null @@ -1,86 +0,0 @@ -package eu.kanade.tachiyomi.ui.base.controller - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.app.AppCompatActivity -import androidx.viewbinding.ViewBinding -import com.bluelinelabs.conductor.Controller -import com.bluelinelabs.conductor.ControllerChangeHandler -import com.bluelinelabs.conductor.ControllerChangeType -import eu.kanade.tachiyomi.util.system.logcat -import eu.kanade.tachiyomi.util.view.hideKeyboard -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.cancel - -abstract class BaseController(bundle: Bundle? = null) : Controller(bundle) { - - protected lateinit var binding: VB - private set - - lateinit var viewScope: CoroutineScope - - init { - retainViewMode = RetainViewMode.RETAIN_DETACH - - addLifecycleListener( - object : LifecycleListener() { - override fun postCreateView(controller: Controller, view: View) { - onViewCreated(view) - } - - override fun preCreateView(controller: Controller) { - viewScope = MainScope() - logcat { "Create view for ${controller.instance()}" } - } - - override fun preAttach(controller: Controller, view: View) { - logcat { "Attach view for ${controller.instance()}" } - } - - override fun preDetach(controller: Controller, view: View) { - logcat { "Detach view for ${controller.instance()}" } - } - - override fun preDestroyView(controller: Controller, view: View) { - viewScope.cancel() - logcat { "Destroy view for ${controller.instance()}" } - } - }, - ) - } - - abstract fun createBinding(inflater: LayoutInflater): VB - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View { - binding = createBinding(inflater) - return binding.root - } - - open fun onViewCreated(view: View) {} - - override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { - view?.hideKeyboard() - - if (type.isEnter) { - setTitle() - setHasOptionsMenu(true) - } - - super.onChangeStarted(handler, type) - } - - open fun getTitle(): String? { - return null - } - - fun setTitle(title: String? = null) { - (activity as? AppCompatActivity)?.supportActionBar?.title = title ?: getTitle() - } - - private fun Controller.instance(): String { - return "${javaClass.simpleName}@${Integer.toHexString(hashCode())}" - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ComposeController.kt deleted file mode 100644 index de77e41f3..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ComposeController.kt +++ /dev/null @@ -1,49 +0,0 @@ -package eu.kanade.tachiyomi.ui.base.controller - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import androidx.activity.OnBackPressedDispatcherOwner -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import eu.kanade.presentation.util.LocalRouter -import eu.kanade.tachiyomi.databinding.ComposeControllerBinding -import eu.kanade.tachiyomi.util.view.setComposeContent - -/** - * Basic Compose controller without a presenter. - */ -abstract class BasicFullComposeController(bundle: Bundle? = null) : - BaseController(bundle), - ComposeContentController { - - override fun createBinding(inflater: LayoutInflater) = - ComposeControllerBinding.inflate(inflater) - - override fun onViewCreated(view: View) { - super.onViewCreated(view) - - binding.root.apply { - setComposeContent { - CompositionLocalProvider(LocalRouter provides router) { - ComposeContent() - } - } - } - } - - // Let Compose view handle this - override fun handleBack(): Boolean { - val dispatcher = (activity as? OnBackPressedDispatcherOwner)?.onBackPressedDispatcher ?: return false - return if (dispatcher.hasEnabledCallbacks()) { - dispatcher.onBackPressed() - true - } else { - false - } - } -} - -interface ComposeContentController { - @Composable fun ComposeContent() -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt deleted file mode 100644 index 12b9c9619..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt +++ /dev/null @@ -1,26 +0,0 @@ -package eu.kanade.tachiyomi.ui.base.controller - -import androidx.core.net.toUri -import com.bluelinelabs.conductor.Controller -import com.bluelinelabs.conductor.Router -import com.bluelinelabs.conductor.RouterTransaction -import eu.kanade.tachiyomi.ui.base.changehandler.OneWayFadeChangeHandler -import eu.kanade.tachiyomi.util.system.openInBrowser - -fun Router.setRoot(controller: Controller, id: Int) { - setRoot(controller.withFadeTransaction().tag(id.toString())) -} - -fun Router.pushController(controller: Controller) { - pushController(controller.withFadeTransaction()) -} - -fun Controller.withFadeTransaction(): RouterTransaction { - return RouterTransaction.with(this) - .pushChangeHandler(OneWayFadeChangeHandler()) - .popChangeHandler(OneWayFadeChangeHandler()) -} - -fun Controller.openInBrowser(url: String) { - activity?.openInBrowser(url.toUri()) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/DialogController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/DialogController.kt deleted file mode 100644 index 63f63e1d0..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/DialogController.kt +++ /dev/null @@ -1,119 +0,0 @@ -package eu.kanade.tachiyomi.ui.base.controller - -import android.app.Dialog -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.bluelinelabs.conductor.Controller -import com.bluelinelabs.conductor.Router -import com.bluelinelabs.conductor.RouterTransaction -import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler - -/** - * A controller that displays a dialog window, floating on top of its activity's window. - * This is a wrapper over [Dialog] object like [android.app.DialogFragment]. - * - * Implementations should override this class and implement [.onCreateDialog] to create a custom dialog, such as an [android.app.AlertDialog] - */ -abstract class DialogController : Controller { - - protected var dialog: Dialog? = null - private set - - private var dismissed = false - - /** - * Convenience constructor for use when no arguments are needed. - */ - protected constructor() : super(null) - - /** - * Constructor that takes arguments that need to be retained across restarts. - * - * @param args Any arguments that need to be retained. - */ - protected constructor(args: Bundle?) : super(args) - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View { - dialog = onCreateDialog(savedViewState) - dialog!!.setOwnerActivity(activity!!) - dialog!!.setOnDismissListener { dismissDialog() } - if (savedViewState != null) { - val dialogState = savedViewState.getBundle(SAVED_DIALOG_STATE_TAG) - if (dialogState != null) { - dialog!!.onRestoreInstanceState(dialogState) - } - } - return View(activity) // stub view - } - - override fun onSaveViewState(view: View, outState: Bundle) { - super.onSaveViewState(view, outState) - val dialogState = dialog!!.onSaveInstanceState() - outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState) - } - - override fun onAttach(view: View) { - super.onAttach(view) - dialog!!.show() - } - - override fun onDetach(view: View) { - super.onDetach(view) - dialog!!.hide() - } - - override fun onDestroyView(view: View) { - super.onDestroyView(view) - dialog!!.setOnDismissListener(null) - dialog!!.dismiss() - dialog = null - } - - /** - * Display the dialog, create a transaction and pushing the controller. - * @param router The router on which the transaction will be applied - */ - open fun showDialog(router: Router) { - showDialog(router, null) - } - - /** - * Display the dialog, create a transaction and pushing the controller. - * @param router The router on which the transaction will be applied - * @param tag The tag for this controller - */ - fun showDialog(router: Router, tag: String?) { - dismissed = false - router.pushController( - RouterTransaction.with(this) - .pushChangeHandler(SimpleSwapChangeHandler(false)) - .popChangeHandler(SimpleSwapChangeHandler(false)) - .tag(tag), - ) - } - - /** - * Dismiss the dialog and pop this controller - */ - fun dismissDialog() { - if (dismissed) { - return - } - router.popController(this) - dismissed = true - } - - /** - * Build your own custom Dialog container such as an [android.app.AlertDialog] - * - * @param savedViewState A bundle for the view's state, which would have been created in [.onSaveViewState] or `null` if no saved state exists. - * @return Return a new Dialog instance to be displayed by the Controller - */ - protected abstract fun onCreateDialog(savedViewState: Bundle?): Dialog - - companion object { - private const val SAVED_DIALOG_STATE_TAG = "android:savedDialogState" - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/RootController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/RootController.kt deleted file mode 100644 index 17b27f911..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/RootController.kt +++ /dev/null @@ -1,3 +0,0 @@ -package eu.kanade.tachiyomi.ui.base.controller - -interface RootController diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseController.kt deleted file mode 100644 index 18f6c44c0..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseController.kt +++ /dev/null @@ -1,27 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.ui.base.controller.RootController - -class BrowseController : BasicFullComposeController, RootController { - - @Suppress("unused") - constructor(bundle: Bundle? = null) : this(bundle?.getBoolean(TO_EXTENSIONS_EXTRA) ?: false) - - constructor(toExtensions: Boolean = false) : super( - bundleOf(TO_EXTENSIONS_EXTRA to toExtensions), - ) - - private val toExtensions = args.getBoolean(TO_EXTENSIONS_EXTRA, false) - - @Composable - override fun ComposeContent() { - Navigator(screen = BrowseScreen(toExtensions = toExtensions)) - } -} - -private const val TO_EXTENSIONS_EXTRA = "to_extensions" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt similarity index 75% rename from app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseScreen.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt index 926a4e130..839980661 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt @@ -1,18 +1,24 @@ package eu.kanade.tachiyomi.ui.browse +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.tab.LocalTabNavigator +import cafe.adriel.voyager.navigator.tab.TabOptions import eu.kanade.core.prefs.asState import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.components.TabbedScreen +import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel import eu.kanade.tachiyomi.ui.browse.extension.extensionsTab @@ -24,9 +30,21 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -data class BrowseScreen( - private val toExtensions: Boolean, -) : Screen { +data class BrowseTab( + private val toExtensions: Boolean = false, +) : Tab { + + override val options: TabOptions + @Composable + get() { + val isSelected = LocalTabNavigator.current.current.key == key + val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_browse_enter) + return TabOptions( + index = 3u, + title = stringResource(R.string.browse), + icon = rememberAnimatedVectorPainter(image, isSelected), + ) + } @Composable override fun Content() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt index 0b0799fd9..79dd386e3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.domain.source.interactor.GetRemoteManga import eu.kanade.presentation.browse.FeedAddDialog @@ -17,17 +18,15 @@ import eu.kanade.presentation.browse.FeedDeleteConfirmDialog import eu.kanade.presentation.browse.FeedScreen import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.TabContent -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.pushController -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController -import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen +import eu.kanade.tachiyomi.ui.manga.MangaScreen import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @Composable fun Screen.feedTab(): TabContent { - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { FeedScreenModel() } val state by screenModel.state.collectAsState() @@ -48,25 +47,25 @@ fun Screen.feedTab(): TabContent { contentPadding = contentPadding, onClickSavedSearch = { savedSearch, source -> screenModel.sourcePreferences.lastUsedSource().set(savedSearch.source) - router.pushController( - BrowseSourceController( - source, + navigator.push( + BrowseSourceScreen( + source.id, savedSearch = savedSearch.id, ), ) }, onClickSource = { source -> screenModel.sourcePreferences.lastUsedSource().set(source.id) - router.pushController( - BrowseSourceController( - source, + navigator.push( + BrowseSourceScreen( + source.id, GetRemoteManga.QUERY_LATEST, ), ) }, onClickDelete = screenModel::openDeleteDialog, onClickManga = { manga -> - router.pushController(MangaController(manga.id, true)) + navigator.push(MangaScreen(manga.id, true)) }, getMangaState = { manga, source -> screenModel.getManga(initialManga = manga, source = source) }, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationController.kt deleted file mode 100644 index 25b7e26a3..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationController.kt +++ /dev/null @@ -1,43 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.design - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import com.bluelinelabs.conductor.Router -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction -import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListController -import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationProcedureConfig - -class PreMigrationController(bundle: Bundle? = null) : BasicFullComposeController(bundle) { - - constructor(mangaIds: List) : this( - bundleOf( - MANGA_IDS_EXTRA to mangaIds.toLongArray(), - ), - ) - - private val config: LongArray = args.getLongArray(MANGA_IDS_EXTRA) ?: LongArray(0) - - @Composable - override fun ComposeContent() { - Navigator(screen = PreMigrationScreen(config.toList())) - } - - companion object { - private const val MANGA_IDS_EXTRA = "manga_ids" - - fun navigateToMigration(skipPre: Boolean, router: Router, mangaIds: List) { - router.pushController( - if (skipPre) { - MigrationListController( - MigrationProcedureConfig(mangaIds, null), - ) - } else { - PreMigrationController(mangaIds) - }.withFadeTransaction().tag(MigrationListController.TAG), - ) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt index 369e0954b..e2bcb2039 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt @@ -45,7 +45,6 @@ import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.ExtendedFloatingActionButton import eu.kanade.presentation.components.OverflowMenu import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.databinding.PreMigrationListBinding import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen @@ -58,7 +57,6 @@ class PreMigrationScreen(val mangaIds: List) : Screen { override fun Content() { val screenModel = rememberScreenModel { PreMigrationScreenModel() } val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - val router = LocalRouter.currentOrThrow val navigator = LocalNavigator.currentOrThrow var fabExpanded by remember { mutableStateOf(true) } val items by screenModel.state.collectAsState() @@ -99,12 +97,7 @@ class PreMigrationScreen(val mangaIds: List) : Screen { topBar = { AppBar( title = stringResource(R.string.select_sources), - navigateUp = { - when { - navigator.canPop -> navigator.pop() - else -> router.popCurrentController() - } - }, + navigateUp = navigator::pop, scrollBehavior = scrollBehavior, actions = { IconButton(onClick = { screenModel.massSelect(false) }) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListController.kt deleted file mode 100644 index a557f8488..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListController.kt +++ /dev/null @@ -1,30 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.process - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.util.system.getSerializableCompat - -class MigrationListController(bundle: Bundle? = null) : - BasicFullComposeController(bundle) { - - constructor(config: MigrationProcedureConfig) : this( - bundleOf( - CONFIG_EXTRA to config, - ), - ) - - val config = args.getSerializableCompat(CONFIG_EXTRA)!! - - @Composable - override fun ComposeContent() { - Navigator(screen = MigrationListScreen(config)) - } - - companion object { - const val CONFIG_EXTRA = "config_extra" - const val TAG = "migration_list" - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreen.kt index 6b40d4488..4128c60dc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreen.kt @@ -15,14 +15,9 @@ import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.browse.MigrationListScreen import eu.kanade.presentation.browse.components.MigrationExitDialog import eu.kanade.presentation.browse.components.MigrationMangaDialog -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.changehandler.OneWayFadeChangeHandler -import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen -import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.system.toast @@ -39,7 +34,6 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen val unfinishedCount by screenModel.unfinishedCount.collectAsState() val dialog by screenModel.dialog.collectAsState() val navigator = LocalNavigator.currentOrThrow - val router = LocalRouter.currentOrThrow val context = LocalContext.current LaunchedEffect(items) { if (items.isEmpty()) { @@ -52,11 +46,7 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen ), ) if (!screenModel.hideNotFound) { - if (navigator.canPop) { - navigator.pop() - } else { - router.popCurrentController() - } + navigator.pop() } } } @@ -71,56 +61,29 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen LaunchedEffect(screenModel) { screenModel.navigateOut.collect { - if (navigator.canPop) { - if (items.size == 1) { - val hasDetails = navigator.items.any { it is MangaScreen } - if (hasDetails) { - val manga = (items.firstOrNull()?.searchResult?.value as? MigratingManga.SearchResult.Result)?.let { - screenModel.getManga(it.id) - } - withUIContext { - if (manga != null) { - val newStack = navigator.items.filter { - it !is MangaScreen && - it !is MigrationListScreen && - it !is PreMigrationScreen - } + MangaScreen(manga.id) - navigator replaceAll newStack.first() - navigator.push(newStack.drop(1)) - } else { - navigator.pop() - } - } + if (items.size == 1) { + val hasDetails = navigator.items.any { it is MangaScreen } + if (hasDetails) { + val manga = (items.firstOrNull()?.searchResult?.value as? MigratingManga.SearchResult.Result)?.let { + screenModel.getManga(it.id) } - } else { withUIContext { - navigator.pop() + if (manga != null) { + val newStack = navigator.items.filter { + it !is MangaScreen && + it !is MigrationListScreen && + it !is PreMigrationScreen + } + MangaScreen(manga.id) + navigator replaceAll newStack.first() + navigator.push(newStack.drop(1)) + } else { + navigator.pop() + } } } } else { - if (items.size == 1) { - val hasDetails = router.backstack.any { it.controller is MangaController } - if (hasDetails) { - val manga = (items.firstOrNull()?.searchResult?.value as? MigratingManga.SearchResult.Result)?.let { - screenModel.getManga(it.id) - } - withUIContext { - if (manga != null) { - val newStack = router.backstack.filter { - it.controller !is MangaController && - it.controller !is MigrationListController && - it.controller !is PreMigrationController - } + MangaController(manga.id).withFadeTransaction() - router.setBackstack(newStack, OneWayFadeChangeHandler()) - } else { - router.popCurrentController() - } - } - } - } else { - withUIContext { - router.popCurrentController() - } + withUIContext { + navigator.pop() } } } @@ -166,13 +129,7 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen MigrationListScreenModel.Dialog.MigrationExitDialog -> { MigrationExitDialog( onDismissRequest = onDismissRequest, - exitMigration = { - if (navigator.canPop) { - navigator.pop() - } else { - router.popCurrentController() - } - }, + exitMigration = navigator::pop, ) } null -> Unit diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt deleted file mode 100644 index 65aad17a5..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt +++ /dev/null @@ -1,34 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.search - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.domain.manga.model.Manga -import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.util.system.getSerializableCompat - -class SourceSearchController(bundle: Bundle) : BasicFullComposeController(bundle) { - - constructor(manga: Manga, source: CatalogueSource, searchQuery: String? = null) : this( - bundleOf( - SOURCE_ID_KEY to source.id, - MANGA_KEY to manga, - SEARCH_QUERY_KEY to searchQuery, - ), - ) - - private var oldManga: Manga = args.getSerializableCompat(MANGA_KEY)!! - private val sourceId = args.getLong(SOURCE_ID_KEY) - private val query = args.getString(SEARCH_QUERY_KEY) - - @Composable - override fun ComposeContent() { - Navigator(screen = SourceSearchScreen(oldManga, sourceId, query)) - } -} - -private const val MANGA_KEY = "oldManga" -private const val SOURCE_ID_KEY = "sourceId" -private const val SEARCH_QUERY_KEY = "searchQuery" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt index 725ff6bc2..c62cefbb0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt @@ -20,13 +20,12 @@ import eu.kanade.presentation.browse.BrowseSourceContent import eu.kanade.presentation.browse.components.BrowseSourceFloatingActionButton import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.SearchToolbar -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel -import eu.kanade.tachiyomi.ui.more.MoreController import eu.kanade.tachiyomi.ui.webview.WebViewActivity +import eu.kanade.tachiyomi.util.Constants data class SourceSearchScreen( private val oldManga: Manga, @@ -38,7 +37,6 @@ data class SourceSearchScreen( override fun Content() { val context = LocalContext.current val uriHandler = LocalUriHandler.current - val router = LocalRouter.currentOrThrow val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId = sourceId, searchQuery = query) } @@ -46,19 +44,12 @@ data class SourceSearchScreen( val snackbarHostState = remember { SnackbarHostState() } - val navigateUp: () -> Unit = { - when { - navigator.canPop -> navigator.pop() - router.backstackSize > 1 -> router.popCurrentController() - } - } - Scaffold( topBar = { scrollBehavior -> SearchToolbar( searchQuery = state.toolbarQuery ?: "", onChangeSearchQuery = screenModel::setToolbarQuery, - onClickCloseSearch = navigateUp, + onClickCloseSearch = navigator::pop, onSearch = { screenModel.search(it) }, scrollBehavior = scrollBehavior, ) @@ -100,7 +91,7 @@ data class SourceSearchScreen( val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name) context.startActivity(intent) }, - onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) }, + onHelpClick = { uriHandler.openUri(Constants.URL_HELP) }, onLocalSourceHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) }, onMangaClick = openMigrateDialog, onMangaLongClick = openMigrateDialog, @@ -108,7 +99,7 @@ data class SourceSearchScreen( } LaunchedEffect(state.filters) { - screenModel.initFilterSheet(context, router) + screenModel.initFilterSheet(context, navigator) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesController.kt deleted file mode 100644 index 09c555e8d..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesController.kt +++ /dev/null @@ -1,15 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.sources - -import androidx.compose.runtime.Composable -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -class MigrationSourcesController : BasicFullComposeController() { - - @Composable - override fun ComposeContent() { - Navigator(screen = MigrationSourcesScreen()) - } -} - -private const val HELP_URL = "https://tachiyomi.org/help/guides/source-migration/" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesFilterController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesFilterController.kt deleted file mode 100755 index 227a257d7..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesFilterController.kt +++ /dev/null @@ -1,17 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.source - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.presentation.util.LocalRouter -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -class SourceFilterController : BasicFullComposeController() { - - @Composable - override fun ComposeContent() { - CompositionLocalProvider(LocalRouter provides router) { - Navigator(screen = SourcesFilterScreen()) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesFilterScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesFilterScreen.kt index 16bb1df4a..9752a58cd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesFilterScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesFilterScreen.kt @@ -7,10 +7,10 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalContext import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.browse.SourcesFilterScreen import eu.kanade.presentation.components.LoadingScreen -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.toast @@ -18,7 +18,7 @@ class SourcesFilterScreen : Screen { @Composable override fun Content() { - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { SourcesFilterScreenModel() } val state by screenModel.state.collectAsState() @@ -31,7 +31,7 @@ class SourcesFilterScreen : Screen { val context = LocalContext.current LaunchedEffect(Unit) { context.toast(R.string.internal_error) - router.popCurrentController() + navigator.pop() } return } @@ -39,7 +39,7 @@ class SourcesFilterScreen : Screen { val successState = state as SourcesFilterState.Success SourcesFilterScreen( - navigateUp = router::popCurrentController, + navigateUp = navigator::pop, state = successState, onClickLanguage = screenModel::toggleLanguage, onClickSource = screenModel::toggleSource, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt index cadd2f67b..1eb528268 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.domain.source.interactor.GetRemoteManga.Companion.QUERY_POPULAR import eu.kanade.presentation.browse.SourceCategoriesDialog @@ -17,14 +18,12 @@ import eu.kanade.presentation.browse.SourceOptionsDialog import eu.kanade.presentation.browse.SourcesScreen import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.TabContent -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.pushController import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen.SmartSearchConfig -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController -import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedController -import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController -import exh.ui.smartsearch.SmartSearchController +import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen +import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedScreen +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen +import exh.ui.smartsearch.SmartSearchScreen import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -32,7 +31,7 @@ import kotlinx.coroutines.launch fun Screen.sourcesTab( smartSearchConfig: SmartSearchConfig? = null, ): TabContent { - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { SourcesScreenModel(smartSearchConfig = smartSearchConfig) } val state by screenModel.state.collectAsState() @@ -47,12 +46,12 @@ fun Screen.sourcesTab( AppBar.Action( title = stringResource(R.string.action_global_search), icon = Icons.Outlined.TravelExplore, - onClick = { router.pushController(GlobalSearchController()) }, + onClick = { navigator.push(GlobalSearchScreen()) }, ), AppBar.Action( title = stringResource(R.string.action_filter), icon = Icons.Outlined.FilterList, - onClick = { router.pushController(SourceFilterController()) }, + onClick = { navigator.push(SourcesFilterScreen()) }, ), ) } else { @@ -65,13 +64,13 @@ fun Screen.sourcesTab( contentPadding = contentPadding, onClickItem = { source, query -> // SY --> - val controller = when { - smartSearchConfig != null -> SmartSearchController(source.id, smartSearchConfig) - (query.isBlank() || query == QUERY_POPULAR) && screenModel.useNewSourceNavigation -> SourceFeedController(source.id) - else -> BrowseSourceController(source.id, query) + val screen = when { + smartSearchConfig != null -> SmartSearchScreen(source.id, smartSearchConfig) + (query.isBlank() || query == QUERY_POPULAR) && screenModel.useNewSourceNavigation -> SourceFeedScreen(source.id) + else -> BrowseSourceScreen(source.id, query) } screenModel.onOpenSource(source) - router.pushController(controller) + navigator.push(screen) // SY <-- }, onClickPin = screenModel::togglePin, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt deleted file mode 100644 index 564909115..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt +++ /dev/null @@ -1,147 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.source.browse - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import cafe.adriel.voyager.navigator.CurrentScreen -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.consumeAsFlow -import kotlinx.coroutines.launch - -class BrowseSourceController(bundle: Bundle) : BasicFullComposeController(bundle) { - - constructor( - sourceId: Long, - query: String? = null, - // SY --> - smartSearchConfig: SourcesScreen.SmartSearchConfig? = null, - savedSearch: Long? = null, - filterList: String? = null, - // SY <-- - ) : this( - Bundle().apply { - putLong(SOURCE_ID_KEY, sourceId) - if (query != null) { - putString(SEARCH_QUERY_KEY, query) - } - - // SY --> - if (smartSearchConfig != null) { - putSerializable(SMART_SEARCH_CONFIG_KEY, smartSearchConfig) - } - - if (savedSearch != null) { - putLong(SAVED_SEARCH_CONFIG_KEY, savedSearch) - } - - if (filterList != null) { - putString(FILTERS_CONFIG_KEY, filterList) - } - // SY <-- - }, - ) - - constructor( - source: CatalogueSource, - query: String? = null, - // SY --> - smartSearchConfig: SourcesScreen.SmartSearchConfig? = null, - savedSearch: Long? = null, - filterList: String? = null, - // SY <-- - ) : this( - source.id, - query, - smartSearchConfig, - savedSearch, - filterList, - ) - - constructor( - source: Source, - query: String? = null, - // SY --> - smartSearchConfig: SourcesScreen.SmartSearchConfig? = null, - savedSearch: Long? = null, - filterList: String? = null, - // SY <-- - ) : this( - source.id, - query, - smartSearchConfig, - savedSearch, - filterList, - ) - - private val sourceId = args.getLong(SOURCE_ID_KEY) - private val initialQuery = args.getString(SEARCH_QUERY_KEY) - - // SY --> - private val filtersJson = args.getString(FILTERS_CONFIG_KEY) - private val savedSearch = args.getLong(SAVED_SEARCH_CONFIG_KEY, 0).takeUnless { it == 0L } - // SY <-- - - private val queryEvent = Channel() - - @Composable - override fun ComposeContent() { - Navigator( - screen = BrowseSourceScreen( - sourceId = sourceId, - query = initialQuery, - // SY --> - filtersJson = filtersJson, - savedSearch = savedSearch, - // SY <-- - ), - ) { navigator -> - CurrentScreen() - - LaunchedEffect(Unit) { - queryEvent.consumeAsFlow() - .collectLatest { - val screen = (navigator.lastItem as? BrowseSourceScreen) - when (it) { - is BrowseSourceScreen.SearchType.Genre -> screen?.searchGenre(it.txt) - is BrowseSourceScreen.SearchType.Text -> screen?.search(it.txt) - } - } - } - } - } - - /** - * Restarts the request with a new query. - * - * @param newQuery the new query. - */ - fun searchWithQuery(newQuery: String) { - viewScope.launch { queryEvent.send(BrowseSourceScreen.SearchType.Text(newQuery)) } - } - - /** - * Attempts to restart the request with a new genre-filtered query. - * If the genre name can't be found the filters, - * the standard searchWithQuery search method is used instead. - * - * @param genreName the name of the genre - */ - fun searchWithGenre(genreName: String) { - viewScope.launch { queryEvent.send(BrowseSourceScreen.SearchType.Genre(genreName)) } - } -} - -private const val SOURCE_ID_KEY = "sourceId" -private const val SEARCH_QUERY_KEY = "searchQuery" - -// SY --> -private const val SMART_SEARCH_CONFIG_KEY = "smartSearchConfig" -private const val SAVED_SEARCH_CONFIG_KEY = "savedSearch" -private const val FILTERS_CONFIG_KEY = "filters" -// SY <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt index 1ff2e80cd..e619c4c3a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.ui.browse.source.browse -import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement @@ -52,16 +51,15 @@ import eu.kanade.presentation.components.ChangeCategoryDialog import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.DuplicateMangaDialog import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.ui.base.controller.pushController import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen -import eu.kanade.tachiyomi.ui.category.CategoryController -import eu.kanade.tachiyomi.ui.manga.MangaController -import eu.kanade.tachiyomi.ui.more.MoreController +import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen +import eu.kanade.tachiyomi.ui.category.CategoryScreen +import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.webview.WebViewActivity +import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.util.lang.launchIO import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collectLatest @@ -73,6 +71,7 @@ data class BrowseSourceScreen( // SY --> private val filtersJson: String? = null, private val savedSearch: Long? = null, + private val smartSearchConfig: SourcesScreen.SmartSearchConfig? = null, // SY <-- ) : Screen { @@ -80,7 +79,6 @@ data class BrowseSourceScreen( @Composable override fun Content() { - val router = LocalRouter.currentOrThrow val navigator = LocalNavigator.currentOrThrow val scope = rememberCoroutineScope() val context = LocalContext.current @@ -109,13 +107,6 @@ data class BrowseSourceScreen( context.startActivity(intent) } - val navigateUp: () -> Unit = { - when { - navigator.canPop -> navigator.pop() - router.backstackSize > 1 -> router.popCurrentController() - } - } - Scaffold( topBar = { Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) { @@ -125,7 +116,7 @@ data class BrowseSourceScreen( source = screenModel.source, displayMode = screenModel.displayMode, onDisplayModeChange = { screenModel.displayMode = it }, - navigateUp = navigateUp, + navigateUp = navigator::pop, onWebViewClick = onWebViewClick, onHelpClick = onHelpClick, onSearch = { screenModel.search(it) }, @@ -227,9 +218,9 @@ data class BrowseSourceScreen( snackbarHostState = snackbarHostState, contentPadding = paddingValues, onWebViewClick = onWebViewClick, - onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) }, + onHelpClick = { uriHandler.openUri(Constants.URL_HELP) }, onLocalSourceHelpClick = onHelpClick, - onMangaClick = { router.pushController(MangaController(it.id, true)) }, + onMangaClick = { navigator.push(MangaScreen(it.id, true, smartSearchConfig)) }, onMangaLongClick = { manga -> scope.launchIO { val duplicateManga = screenModel.getDuplicateLibraryManga(manga) @@ -256,7 +247,7 @@ data class BrowseSourceScreen( DuplicateMangaDialog( onDismissRequest = onDismissRequest, onConfirm = { screenModel.addFavorite(dialog.manga) }, - onOpenManga = { router.pushController(MangaController(dialog.duplicate.id)) }, + onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, duplicateFrom = screenModel.getSourceOrStub(dialog.duplicate), ) } @@ -273,9 +264,7 @@ data class BrowseSourceScreen( ChangeCategoryDialog( initialSelection = dialog.initialSelection, onDismissRequest = onDismissRequest, - onEditCategories = { - router.pushController(CategoryController()) - }, + onEditCategories = { navigator.push(CategoryScreen()) }, onConfirm = { include, _ -> screenModel.changeMangaFavorite(dialog.manga) screenModel.moveMangaToCategories(dialog.manga, include) @@ -298,10 +287,8 @@ data class BrowseSourceScreen( else -> {} } - BackHandler(onBack = navigateUp) - LaunchedEffect(state.filters) { - screenModel.initFilterSheet(context, router) + screenModel.initFilterSheet(context, navigator) } LaunchedEffect(Unit) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt index 524ad877a..f312c1680 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt @@ -14,7 +14,7 @@ import androidx.paging.cachedIn import androidx.paging.map import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.coroutineScope -import com.bluelinelabs.conductor.Router +import cafe.adriel.voyager.navigator.Navigator import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.core.prefs.CheckboxState import eu.kanade.core.prefs.asState @@ -454,7 +454,7 @@ open class BrowseSourceScreenModel( mutableState.update { it.copy(toolbarQuery = query) } } - open fun initFilterSheet(context: Context, router: Router) { + open fun initFilterSheet(context: Context, navigator: Navigator) { val state = state.value /*if (state.filters.isEmpty()) { return @@ -463,7 +463,7 @@ open class BrowseSourceScreenModel( filterSheet = SourceFilterSheet( context = context, // SY --> - router = router, + navigator = navigator, source = source, searches = emptyList(), // SY <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterSheet.kt index 1bad2a3a4..2eef9692c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterSheet.kt @@ -7,7 +7,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.ConcatAdapter -import com.bluelinelabs.conductor.Router +import cafe.adriel.voyager.navigator.Navigator import com.google.android.material.chip.Chip import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible @@ -23,7 +23,7 @@ import exh.source.getMainSource class SourceFilterSheet( context: Context, // SY --> - router: Router, + navigator: Navigator, source: CatalogueSource, searches: List = emptyList(), // SY <-- @@ -41,7 +41,7 @@ class SourceFilterSheet( // SY --> searches = searches, source = source, - router = router, + navigator = navigator, dismissSheet = ::dismiss, // SY <-- ) @@ -84,7 +84,7 @@ class SourceFilterSheet( // SY --> searches: List = emptyList(), source: CatalogueSource? = null, - router: Router? = null, + navigator: Navigator? = null, dismissSheet: (() -> Unit)? = null, // SY <-- ) : @@ -116,10 +116,10 @@ class SourceFilterSheet( // SY --> recycler.adapter = ConcatAdapter( listOfNotNull( - router?.let { + navigator?.let { source?.getMainSource() ?.let { - MangaDexFabHeaderAdapter(router, it) { + MangaDexFabHeaderAdapter(navigator, it) { dismissSheet?.invoke() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt deleted file mode 100644 index efde60f9a..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt +++ /dev/null @@ -1,37 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.source.feed - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -class SourceFeedController : BasicFullComposeController { - - constructor(source: CatalogueSource) : super( - bundleOf( - SOURCE_EXTRA to source.id, - ), - ) - - constructor(sourceId: Long) : super( - bundleOf( - SOURCE_EXTRA to sourceId, - ), - ) - - @Suppress("unused") - constructor(bundle: Bundle) : super(bundle) - - val sourceId = args.getLong(SOURCE_EXTRA) - - @Composable - override fun ComposeContent() { - Navigator(screen = SourceFeedScreen(sourceId)) - } - - companion object { - const val SOURCE_EXTRA = "source" - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt index a060d3a39..0b09dd72c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt @@ -13,19 +13,16 @@ import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow -import com.bluelinelabs.conductor.Router import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.source.interactor.GetRemoteManga import eu.kanade.presentation.browse.SourceFeedScreen import eu.kanade.presentation.browse.components.FailedToLoadSavedSearchDialog import eu.kanade.presentation.browse.components.SourceFeedAddDialog import eu.kanade.presentation.browse.components.SourceFeedDeleteDialog -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController +import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.util.lang.launchUI @@ -47,19 +44,18 @@ class SourceFeedScreen(val sourceId: Long) : Screen { val screenModel = rememberScreenModel { SourceFeedScreenModel(sourceId) } val state by screenModel.state.collectAsState() val navigator = LocalNavigator.currentOrThrow - val router = LocalRouter.currentOrThrow SourceFeedScreen( name = screenModel.source.name, isLoading = state.isLoading, items = state.items, onFabClick = if (state.filters.isEmpty()) null else { { filterSheet?.show() } }, - onClickBrowse = { onBrowseClick(router, screenModel.source) }, - onClickLatest = { onLatestClick(router, screenModel.source) }, - onClickSavedSearch = { onSavedSearchClick(router, screenModel.source, it) }, + onClickBrowse = { onBrowseClick(navigator, screenModel.source) }, + onClickLatest = { onLatestClick(navigator, screenModel.source) }, + onClickSavedSearch = { onSavedSearchClick(navigator, screenModel.source, it) }, onClickDelete = screenModel::openDeleteFeed, onClickManga = { onMangaClick(navigator, it) }, - onClickSearch = { onSearchClick(router, screenModel.source, it) }, + onClickSearch = { onSearchClick(navigator, screenModel.source, it) }, searchQuery = state.searchQuery, onSearchQueryChange = screenModel::search, isIncognitoMode = screenModel.isIncognitoMode, @@ -102,7 +98,7 @@ class SourceFeedScreen(val sourceId: Long) : Screen { val context = LocalContext.current LaunchedEffect(state.filters) { - initFilterSheet(state, screenModel, scope, context, router) + initFilterSheet(state, screenModel, scope, context, navigator) } } @@ -111,28 +107,28 @@ class SourceFeedScreen(val sourceId: Long) : Screen { screenModel: SourceFeedScreenModel, viewScope: CoroutineScope, context: Context, - router: Router, + navigator: Navigator, ) { val filterSerializer = FilterSerializer() filterSheet = SourceFilterSheet( - context, + context = context, // SY --> - router, - screenModel.source, - emptyList(), + navigator = navigator, + source = screenModel.source, + searches = emptyList(), // SY <-- onFilterClicked = { val allDefault = state.filters == screenModel.source.getFilterList() filterSheet?.dismiss() if (allDefault) { onBrowseClick( - router, + navigator, screenModel.source.id, state.searchQuery?.nullIfBlank(), ) } else { onBrowseClick( - router, + navigator, screenModel.source.id, state.searchQuery?.nullIfBlank(), filters = Json.encodeToString(filterSerializer.serialize(state.filters)), @@ -164,7 +160,7 @@ class SourceFeedScreen(val sourceId: Long) : Screen { if (!allDefault) { onBrowseClick( - router, + navigator, screenModel.source.id, search = state.searchQuery?.nullIfBlank(), savedSearch = search.id, @@ -192,23 +188,23 @@ class SourceFeedScreen(val sourceId: Long) : Screen { navigator.push(MangaScreen(manga.id, true)) } - fun onBrowseClick(router: Router, sourceId: Long, search: String? = null, savedSearch: Long? = null, filters: String? = null) { - router.replaceTopController(BrowseSourceController(sourceId, search, savedSearch = savedSearch, filterList = filters).withFadeTransaction()) + fun onBrowseClick(navigator: Navigator, sourceId: Long, search: String? = null, savedSearch: Long? = null, filters: String? = null) { + navigator.replace(BrowseSourceScreen(sourceId, search, savedSearch = savedSearch, filtersJson = filters)) } - private fun onLatestClick(router: Router, source: CatalogueSource) { - router.replaceTopController(BrowseSourceController(source, GetRemoteManga.QUERY_LATEST).withFadeTransaction()) + private fun onLatestClick(navigator: Navigator, source: CatalogueSource) { + navigator.replace(BrowseSourceScreen(source.id, GetRemoteManga.QUERY_LATEST)) } - fun onBrowseClick(router: Router, source: CatalogueSource) { - router.replaceTopController(BrowseSourceController(source, GetRemoteManga.QUERY_POPULAR).withFadeTransaction()) + fun onBrowseClick(navigator: Navigator, source: CatalogueSource) { + navigator.replace(BrowseSourceScreen(source.id, GetRemoteManga.QUERY_POPULAR)) } - private fun onSavedSearchClick(router: Router, source: CatalogueSource, savedSearch: SavedSearch) { - router.replaceTopController(BrowseSourceController(source, savedSearch = savedSearch.id).withFadeTransaction()) + private fun onSavedSearchClick(navigator: Navigator, source: CatalogueSource, savedSearch: SavedSearch) { + navigator.replace(BrowseSourceScreen(source.id, savedSearch = savedSearch.id)) } - private fun onSearchClick(router: Router, source: CatalogueSource, query: String) { - onBrowseClick(router, source.id, query.nullIfBlank()) + private fun onSearchClick(navigator: Navigator, source: CatalogueSource, query: String) { + onBrowseClick(navigator, source.id, query.nullIfBlank()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt index 130fd902f..820765a9a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt @@ -49,12 +49,6 @@ import xyz.nulldev.ts.api.http.serializer.FilterSerializer import java.util.concurrent.Executors import eu.kanade.domain.manga.model.Manga as DomainManga -/** - * Presenter of [SourceFeedController] - * Function calls should be done from here. UI calls should be done from the controller. - * - * @param source the source. - */ open class SourceFeedScreenModel( val sourceId: Long, private val sourceManager: SourceManager = Injekt.get(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt deleted file mode 100644 index 94c534112..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt +++ /dev/null @@ -1,25 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.source.globalsearch - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.presentation.util.LocalRouter -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -class GlobalSearchController( - val searchQuery: String = "", - val extensionFilter: String = "", -) : BasicFullComposeController() { - - @Composable - override fun ComposeContent() { - CompositionLocalProvider(LocalRouter provides router) { - Navigator( - screen = GlobalSearchScreen( - searchQuery = searchQuery, - extensionFilter = extensionFilter, - ), - ) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt index f8a02fcc4..0e9f4eff4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt @@ -5,12 +5,11 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.browse.GlobalSearchScreen -import eu.kanade.presentation.util.LocalRouter -import eu.kanade.tachiyomi.ui.base.controller.pushController -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController -import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen +import eu.kanade.tachiyomi.ui.manga.MangaScreen class GlobalSearchScreen( val searchQuery: String = "", @@ -19,7 +18,7 @@ class GlobalSearchScreen( @Composable override fun Content() { - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { GlobalSearchScreenModel( @@ -31,7 +30,7 @@ class GlobalSearchScreen( GlobalSearchScreen( state = state, - navigateUp = router::popCurrentController, + navigateUp = navigator::pop, onChangeSearchQuery = screenModel::updateSearchQuery, onSearch = screenModel::search, getManga = { source, manga -> @@ -44,10 +43,10 @@ class GlobalSearchScreen( if (!screenModel.incognitoMode.get()) { screenModel.lastUsedSourceId.set(it.id) } - router.pushController(BrowseSourceController(it.id, state.searchQuery)) + navigator.push(BrowseSourceScreen(it.id, state.searchQuery)) }, - onClickItem = { router.pushController(MangaController(it.id, true)) }, - onLongClickItem = { router.pushController(MangaController(it.id, true)) }, + onClickItem = { navigator.push(MangaScreen(it.id, true)) }, + onLongClickItem = { navigator.push(MangaScreen(it.id, true)) }, ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt deleted file mode 100644 index 9a5357931..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt +++ /dev/null @@ -1,17 +0,0 @@ -package eu.kanade.tachiyomi.ui.category - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.presentation.util.LocalRouter -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -class CategoryController : BasicFullComposeController() { - - @Composable - override fun ComposeContent() { - CompositionLocalProvider(LocalRouter provides router) { - Navigator(screen = CategoryScreen()) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt index 62c600734..69358af1b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt @@ -16,7 +16,6 @@ import eu.kanade.presentation.category.components.CategoryCreateDialog import eu.kanade.presentation.category.components.CategoryDeleteDialog import eu.kanade.presentation.category.components.CategoryRenameDialog import eu.kanade.presentation.components.LoadingScreen -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.flow.collectLatest @@ -29,7 +28,6 @@ class CategoryScreen : Screen { @Composable override fun Content() { val context = LocalContext.current - val router = LocalRouter.currentOrThrow val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { CategoryScreenModel() } @@ -49,12 +47,7 @@ class CategoryScreen : Screen { onClickDelete = { screenModel.showDialog(CategoryDialog.Delete(it)) }, onClickMoveUp = screenModel::moveUp, onClickMoveDown = screenModel::moveDown, - navigateUp = { - when { - navigator.canPop -> navigator.pop() - router.backstackSize > 1 -> router.handleBack() - } - }, + navigateUp = navigator::pop, ) when (val dialog = successState.dialog) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/biometric/BiometricTimesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/biometric/BiometricTimesController.kt deleted file mode 100644 index 3f6d75ee2..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/biometric/BiometricTimesController.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.ui.category.biometric - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.presentation.util.LocalRouter -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -/** - * Controller to manage the lock times for the biometric lock. - */ -class BiometricTimesController : BasicFullComposeController() { - - @Composable - override fun ComposeContent() { - CompositionLocalProvider(LocalRouter provides router) { - Navigator(screen = BiometricTimesScreen()) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/biometric/BiometricTimesScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/biometric/BiometricTimesScreen.kt index f388183ab..6eaa286e2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/biometric/BiometricTimesScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/biometric/BiometricTimesScreen.kt @@ -8,12 +8,12 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import com.google.android.material.timepicker.MaterialTimePicker import eu.kanade.presentation.category.BiometricTimesScreen import eu.kanade.presentation.category.components.CategoryDeleteDialog import eu.kanade.presentation.components.LoadingScreen -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.system.toast @@ -27,7 +27,7 @@ class BiometricTimesScreen : Screen { @Composable override fun Content() { val context = LocalContext.current - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { BiometricTimesScreenModel() } val state by screenModel.state.collectAsState() @@ -43,7 +43,7 @@ class BiometricTimesScreen : Screen { state = successState, onClickCreate = { screenModel.showDialog(BiometricTimesDialog.Create) }, onClickDelete = { screenModel.showDialog(BiometricTimesDialog.Delete(it)) }, - navigateUp = router::popCurrentController, + navigateUp = navigator::pop, ) fun showTimePicker(startTime: Duration? = null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/biometric/BiometricTimesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/biometric/BiometricTimesScreenModel.kt index 0717b928f..d679e9be6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/biometric/BiometricTimesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/biometric/BiometricTimesScreenModel.kt @@ -16,9 +16,6 @@ import kotlinx.coroutines.flow.update import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -/** - * Presenter of [BiometricTimesController]. Used to manage the categories of the library. - */ class BiometricTimesScreenModel( private val preferences: SecurityPreferences = Injekt.get(), ) : StateScreenModel(BiometricTimesScreenState.Loading) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagController.kt deleted file mode 100644 index 6120eca4c..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagController.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.ui.category.genre - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.presentation.util.LocalRouter -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -/** - * Controller to manage the categories for the users' library. - */ -class SortTagController : BasicFullComposeController() { - - @Composable - override fun ComposeContent() { - CompositionLocalProvider(LocalRouter provides router) { - Navigator(screen = SortTagScreen()) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreen.kt index ef186b326..ae9d3dab4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreen.kt @@ -8,11 +8,11 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.category.components.CategoryCreateDialog import eu.kanade.presentation.category.components.CategoryDeleteDialog import eu.kanade.presentation.components.LoadingScreen -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.flow.collectLatest @@ -22,7 +22,7 @@ class SortTagScreen : Screen { @Composable override fun Content() { val context = LocalContext.current - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { SortTagScreenModel() } val state by screenModel.state.collectAsState() @@ -40,7 +40,7 @@ class SortTagScreen : Screen { onClickDelete = { screenModel.showDialog(SortTagDialog.Delete(it)) }, onClickMoveUp = screenModel::moveUp, onClickMoveDown = screenModel::moveDown, - navigateUp = router::popCurrentController, + navigateUp = navigator::pop, ) when (val dialog = successState.dialog) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreenModel.kt index 808786250..9f86b7013 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreenModel.kt @@ -17,9 +17,6 @@ import kotlinx.coroutines.flow.update import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -/** - * Presenter of [SortTagController]. Used to manage the categories of the library. - */ class SortTagScreenModel( private val getSortTag: GetSortTag = Injekt.get(), private val createSortTag: CreateSortTag = Injekt.get(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/repos/RepoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/repos/RepoController.kt deleted file mode 100644 index 83ab8f6e9..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/repos/RepoController.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.ui.category.repos - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.presentation.util.LocalRouter -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -/** - * Controller to manage the categories for the users' library. - */ -class RepoController : BasicFullComposeController() { - - @Composable - override fun ComposeContent() { - CompositionLocalProvider(LocalRouter provides router) { - Navigator(screen = RepoScreen()) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/repos/RepoScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/repos/RepoScreen.kt index fc2467c08..349e991dc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/repos/RepoScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/repos/RepoScreen.kt @@ -8,12 +8,12 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.category.SourceRepoScreen import eu.kanade.presentation.category.components.CategoryCreateDialog import eu.kanade.presentation.category.components.CategoryDeleteDialog import eu.kanade.presentation.components.LoadingScreen -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.flow.collectLatest @@ -23,7 +23,7 @@ class RepoScreen : Screen { @Composable override fun Content() { val context = LocalContext.current - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { RepoScreenModel() } val state by screenModel.state.collectAsState() @@ -39,7 +39,7 @@ class RepoScreen : Screen { state = successState, onClickCreate = { screenModel.showDialog(RepoDialog.Create) }, onClickDelete = { screenModel.showDialog(RepoDialog.Delete(it)) }, - navigateUp = router::popCurrentController, + navigateUp = navigator::pop, ) when (val dialog = successState.dialog) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/repos/RepoScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/repos/RepoScreenModel.kt index 8e43e7e06..4be684754 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/repos/RepoScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/repos/RepoScreenModel.kt @@ -16,9 +16,6 @@ import kotlinx.coroutines.flow.update import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -/** - * Presenter of [RepoController]. Used to manage the repos for the extensions. - */ class RepoScreenModel( private val getSourceRepos: GetSourceRepos = Injekt.get(), private val createSourceRepo: CreateSourceRepo = Injekt.get(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryController.kt deleted file mode 100644 index 5bedb1407..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryController.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.ui.category.sources - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.presentation.util.LocalRouter -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -/** - * Controller to manage the categories for the users' library. - */ -class SourceCategoryController : BasicFullComposeController() { - - @Composable - override fun ComposeContent() { - CompositionLocalProvider(LocalRouter provides router) { - Navigator(screen = SourceCategoryScreen()) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryScreen.kt index 4859f9b83..45a603627 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryScreen.kt @@ -8,13 +8,13 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.category.SourceCategoryScreen import eu.kanade.presentation.category.components.CategoryCreateDialog import eu.kanade.presentation.category.components.CategoryDeleteDialog import eu.kanade.presentation.category.components.CategoryRenameDialog import eu.kanade.presentation.components.LoadingScreen -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.flow.collectLatest @@ -24,7 +24,7 @@ class SourceCategoryScreen : Screen { @Composable override fun Content() { val context = LocalContext.current - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { SourceCategoryScreenModel() } val state by screenModel.state.collectAsState() @@ -41,7 +41,7 @@ class SourceCategoryScreen : Screen { onClickCreate = { screenModel.showDialog(SourceCategoryDialog.Create) }, onClickRename = { screenModel.showDialog(SourceCategoryDialog.Rename(it)) }, onClickDelete = { screenModel.showDialog(SourceCategoryDialog.Delete(it)) }, - navigateUp = router::popCurrentController, + navigateUp = navigator::pop, ) when (val dialog = successState.dialog) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryScreenModel.kt index e3c1716bd..5c86c58f2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryScreenModel.kt @@ -17,9 +17,6 @@ import kotlinx.coroutines.flow.update import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -/** - * Presenter of [SourceCategoryController]. Used to manage the categories of the library. - */ class SourceCategoryScreenModel( private val getSourceCategories: GetSourceCategories = Injekt.get(), private val createSourceCategory: CreateSourceCategory = Injekt.get(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt deleted file mode 100755 index cd2058e2e..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt +++ /dev/null @@ -1,15 +0,0 @@ -package eu.kanade.tachiyomi.ui.download - -import androidx.compose.runtime.Composable -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -/** - * Controller that shows the currently active downloads. - */ -class DownloadController : BasicFullComposeController() { - @Composable - override fun ComposeContent() { - Navigator(screen = DownloadQueueScreen) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreen.kt index b8010fe50..0f68c1fe3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreen.kt @@ -47,6 +47,7 @@ import androidx.core.view.updatePadding import androidx.recyclerview.widget.LinearLayoutManager import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.EmptyScreen @@ -54,7 +55,6 @@ import eu.kanade.presentation.components.ExtendedFloatingActionButton import eu.kanade.presentation.components.OverflowMenu import eu.kanade.presentation.components.Pill import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.databinding.DownloadListBinding @@ -66,7 +66,7 @@ object DownloadQueueScreen : Screen { @Composable override fun Content() { val context = LocalContext.current - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val scope = rememberCoroutineScope() val screenModel = rememberScreenModel { DownloadQueueScreenModel() } val downloadList by screenModel.state.collectAsState() @@ -121,7 +121,7 @@ object DownloadQueueScreen : Screen { } } }, - navigateUp = router::popCurrentController, + navigateUp = navigator::pop, actions = { if (downloadList.isNotEmpty()) { OverflowMenu { closeMenu -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryController.kt deleted file mode 100644 index c0410450e..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryController.kt +++ /dev/null @@ -1,26 +0,0 @@ -package eu.kanade.tachiyomi.ui.history - -import androidx.compose.runtime.Composable -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.domain.history.interactor.GetNextChapters -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.ui.base.controller.RootController -import eu.kanade.tachiyomi.util.lang.launchIO -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class HistoryController : BasicFullComposeController(), RootController { - - @Composable - override fun ComposeContent() { - Navigator(screen = HistoryScreen) - } - - fun resumeLastChapterRead() { - val context = activity ?: return - viewScope.launchIO { - val chapter = Injekt.get().await(onlyUnread = false).firstOrNull() - HistoryScreen.openChapter(context, chapter) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt index 4991c89c9..e549eed14 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt @@ -15,6 +15,7 @@ import eu.kanade.domain.history.model.HistoryWithRelations import eu.kanade.presentation.history.HistoryUiModel import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.toDateKey +import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.system.logcat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel @@ -76,6 +77,10 @@ class HistoryScreenModel( } } + suspend fun getNextChapter(): Chapter? { + return withIOContext { getNextChapters.await(onlyUnread = false).firstOrNull() } + } + fun getNextChapterForManga(mangaId: Long, chapterId: Long) { coroutineScope.launchIO { sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt similarity index 62% rename from app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreen.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt index 0e3b943dd..780a5739e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt @@ -1,34 +1,76 @@ package eu.kanade.tachiyomi.ui.history import android.content.Context +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow +import cafe.adriel.voyager.navigator.tab.LocalTabNavigator +import cafe.adriel.voyager.navigator.tab.TabOptions +import eu.kanade.core.prefs.asState import eu.kanade.domain.chapter.model.Chapter +import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.history.HistoryScreen import eu.kanade.presentation.history.components.HistoryDeleteAllDialog import eu.kanade.presentation.history.components.HistoryDeleteDialog -import eu.kanade.presentation.util.LocalRouter +import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.pushController import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.consumeAsFlow +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get -object HistoryScreen : Screen { +object HistoryTab : Tab { private val snackbarHostState = SnackbarHostState() + private val resumeLastChapterReadEvent = Channel() + + override val options: TabOptions + @Composable + get() { + val isSelected = LocalTabNavigator.current.current.key == key + val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_history_enter) + return TabOptions( + index = 2u, + title = stringResource(R.string.label_recent_manga), + icon = rememberAnimatedVectorPainter(image, isSelected), + ) + } + + override suspend fun onReselect(navigator: Navigator) { + resumeLastChapterReadEvent.send(Unit) + } + + // SY --> + @Composable + override fun isEnabled(): Boolean { + val scope = rememberCoroutineScope() + return remember { + Injekt.get().showNavHistory().asState(scope) + }.value + } + // SY <-- + @Composable override fun Content() { - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val context = LocalContext.current val screenModel = rememberScreenModel { HistoryScreenModel() } val state by screenModel.state.collectAsState() @@ -39,7 +81,7 @@ object HistoryScreen : Screen { incognitoMode = screenModel.isIncognitoMode, downloadedOnlyMode = screenModel.isDownloadOnly, onSearchQueryChange = screenModel::updateSearchQuery, - onClickCover = { router.pushController(MangaController(it)) }, + onClickCover = { navigator.push(MangaScreen(it)) }, onClickResume = screenModel::getNextChapterForManga, onDialogChange = screenModel::setDialog, ) @@ -84,6 +126,12 @@ object HistoryScreen : Screen { } } } + + LaunchedEffect(Unit) { + resumeLastChapterReadEvent.consumeAsFlow().collectLatest { + openChapter(context, screenModel.getNextChapter()) + } + } } suspend fun openChapter(context: Context, chapter: Chapter?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt new file mode 100644 index 000000000..ac628392e --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt @@ -0,0 +1,306 @@ +package eu.kanade.tachiyomi.ui.home + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.consumedWindowInsets +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationRailItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.util.fastForEach +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import cafe.adriel.voyager.navigator.tab.LocalTabNavigator +import cafe.adriel.voyager.navigator.tab.TabNavigator +import eu.kanade.core.prefs.asState +import eu.kanade.core.util.fastFilter +import eu.kanade.domain.library.service.LibraryPreferences +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.domain.ui.UiPreferences +import eu.kanade.presentation.components.NavigationBar +import eu.kanade.presentation.components.NavigationRail +import eu.kanade.presentation.components.Scaffold +import eu.kanade.presentation.util.Tab +import eu.kanade.presentation.util.Transition +import eu.kanade.presentation.util.isTabletUi +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.browse.BrowseTab +import eu.kanade.tachiyomi.ui.history.HistoryTab +import eu.kanade.tachiyomi.ui.library.LibraryTab +import eu.kanade.tachiyomi.ui.manga.MangaScreen +import eu.kanade.tachiyomi.ui.more.MoreTab +import eu.kanade.tachiyomi.ui.updates.UpdatesTab +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +object HomeScreen : Screen { + + private val librarySearchEvent = Channel() + private val openTabEvent = Channel() + private val showBottomNavEvent = Channel() + + private val tabs = listOf( + LibraryTab, + UpdatesTab, + HistoryTab, + BrowseTab(), + MoreTab(), + ) + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + // SY --> + val scope = rememberCoroutineScope() + val alwaysShowLabel by remember { + Injekt.get().bottomBarLabels().asState(scope) + } + // SY <-- + TabNavigator( + tab = LibraryTab, + ) { tabNavigator -> + // Provide usable navigator to content screen + CompositionLocalProvider(LocalNavigator provides navigator) { + Row(verticalAlignment = Alignment.CenterVertically) { + if (isTabletUi()) { + NavigationRail { + tabs + // SY --> + .fastFilter { it.isEnabled() } + // SY <-- + .fastForEach { + NavigationRailItem(it/* SY --> */, alwaysShowLabel/* SY <-- */) + } + } + } + Scaffold( + bottomBar = { + if (!isTabletUi()) { + val bottomNavVisible by produceState(initialValue = true) { + showBottomNavEvent.receiveAsFlow().collectLatest { value = it } + } + AnimatedVisibility( + visible = bottomNavVisible, + enter = expandVertically(), + exit = shrinkVertically(), + ) { + NavigationBar { + tabs + // SY --> + .fastFilter { it.isEnabled() } + // SY <-- + .fastForEach { + NavigationBarItem(it/* SY --> */, alwaysShowLabel/* SY <-- */) + } + } + } + } + }, + contentWindowInsets = WindowInsets(0), + ) { contentPadding -> + Box( + modifier = Modifier + .padding(contentPadding) + .consumedWindowInsets(contentPadding), + ) { + AnimatedContent( + targetState = tabNavigator.current, + transitionSpec = { Transition.OneWayFade }, + content = { + tabNavigator.saveableState(key = "currentTab", it) { + it.Content() + } + }, + ) + } + } + } + } + + val goToLibraryTab = { tabNavigator.current = LibraryTab } + BackHandler( + enabled = tabNavigator.current != LibraryTab, + onBack = goToLibraryTab, + ) + + LaunchedEffect(Unit) { + launch { + librarySearchEvent.receiveAsFlow().collectLatest { + goToLibraryTab() + LibraryTab.search(it) + } + } + launch { + openTabEvent.receiveAsFlow().collectLatest { + tabNavigator.current = when (it) { + is Tab.Library -> LibraryTab + Tab.Updates -> UpdatesTab + Tab.History -> HistoryTab + is Tab.Browse -> BrowseTab(it.toExtensions) + is Tab.More -> MoreTab(it.toDownloads) + } + + if (it is Tab.Library && it.mangaIdToOpen != null) { + navigator.push(MangaScreen(it.mangaIdToOpen)) + } + } + } + } + } + } + + @Composable + private fun RowScope.NavigationBarItem(tab: eu.kanade.presentation.util.Tab/* SY --> */, alwaysShowLabel: Boolean/* SY <-- */) { + val tabNavigator = LocalTabNavigator.current + val navigator = LocalNavigator.currentOrThrow + val scope = rememberCoroutineScope() + val selected = tabNavigator.current::class == tab::class + NavigationBarItem( + selected = selected, + onClick = { + if (!selected) { + tabNavigator.current = tab + } else { + scope.launch { tab.onReselect(navigator) } + } + }, + icon = { NavigationIconItem(tab) }, + label = { + Text( + text = tab.options.title, + style = MaterialTheme.typography.labelLarge, + ) + }, + alwaysShowLabel = /* SY --> */alwaysShowLabel, /* SY <-- */ + ) + } + + @Composable + fun NavigationRailItem(tab: eu.kanade.presentation.util.Tab/* SY --> */, alwaysShowLabel: Boolean/* SY <-- */) { + val tabNavigator = LocalTabNavigator.current + val navigator = LocalNavigator.currentOrThrow + val scope = rememberCoroutineScope() + val selected = tabNavigator.current::class == tab::class + NavigationRailItem( + selected = selected, + onClick = { + if (!selected) { + tabNavigator.current = tab + } else { + scope.launch { tab.onReselect(navigator) } + } + }, + icon = { NavigationIconItem(tab) }, + label = { + Text( + text = tab.options.title, + style = MaterialTheme.typography.labelLarge, + ) + }, + alwaysShowLabel = /* SY --> */alwaysShowLabel, /* SY <-- */ + ) + } + + @Composable + private fun NavigationIconItem(tab: eu.kanade.presentation.util.Tab) { + BadgedBox( + badge = { + when { + tab is UpdatesTab -> { + val count by produceState(initialValue = 0) { + val pref = Injekt.get() + combine( + pref.showUpdatesNavBadge().changes(), + pref.unreadUpdatesCount().changes(), + ) { show, count -> if (show) count else 0 } + .collectLatest { value = it } + } + if (count > 0) { + Badge { + val desc = pluralStringResource( + id = R.plurals.notification_chapters_generic, + count = count, + count, + ) + Text( + text = count.toString(), + modifier = Modifier.semantics { contentDescription = desc }, + ) + } + } + } + BrowseTab::class.isInstance(tab) -> { + val count by produceState(initialValue = 0) { + Injekt.get().extensionUpdatesCount().changes() + .collectLatest { value = it } + } + if (count > 0) { + Badge { + val desc = pluralStringResource( + id = R.plurals.update_check_notification_ext_updates, + count = count, + count, + ) + Text( + text = count.toString(), + modifier = Modifier.semantics { contentDescription = desc }, + ) + } + } + } + } + }, + ) { + Icon(painter = tab.options.icon!!, contentDescription = tab.options.title) + } + } + + suspend fun search(query: String) { + librarySearchEvent.send(query) + } + + suspend fun openTab(tab: Tab) { + openTabEvent.send(tab) + } + + suspend fun showBottomNav(show: Boolean) { + showBottomNavEvent.send(show) + } + + sealed class Tab { + data class Library(val mangaIdToOpen: Long? = null) : Tab() + object Updates : Tab() + object History : Tab() + data class Browse(val toExtensions: Boolean = false) : Tab() + data class More(val toDownloads: Boolean) : Tab() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt deleted file mode 100644 index 6209a1a0b..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ /dev/null @@ -1,53 +0,0 @@ -package eu.kanade.tachiyomi.ui.library - -import android.os.Bundle -import android.view.View -import androidx.compose.runtime.Composable -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.domain.category.model.Category -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.ui.base.controller.RootController -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch - -class LibraryController( - bundle: Bundle? = null, -) : BasicFullComposeController(bundle), RootController { - - /** - * Sheet containing filter/sort/display items. - */ - private var settingsSheet: LibrarySettingsSheet? = null - - @Composable - override fun ComposeContent() { - Navigator(screen = LibraryScreen) - } - - override fun onViewCreated(view: View) { - super.onViewCreated(view) - - settingsSheet = LibrarySettingsSheet(router) - viewScope.launch { - LibraryScreen.openSettingsSheetEvent - .collectLatest(::showSettingsSheet) - } - } - - override fun onDestroyView(view: View) { - settingsSheet?.sheetScope?.cancel() - settingsSheet = null - super.onDestroyView(view) - } - - fun showSettingsSheet(category: Category? = null) { - if (category != null) { - settingsSheet?.show(category) - } else { - viewScope.launch { LibraryScreen.requestOpenSettingsSheet() } - } - } - - fun search(query: String) = LibraryScreen.search(query) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt index dd731df0d..b60827057 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt @@ -1,9 +1,9 @@ package eu.kanade.tachiyomi.ui.library +import android.app.Activity import android.content.Context import android.util.AttributeSet import android.view.View -import com.bluelinelabs.conductor.Router import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.SetDisplayModeForCategory @@ -31,11 +31,11 @@ import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy class LibrarySettingsSheet( - router: Router, + activity: Activity, private val trackManager: TrackManager = Injekt.get(), private val setDisplayModeForCategory: SetDisplayModeForCategory = Injekt.get(), private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(), -) : TabbedBottomSheetDialog(router.activity!!) { +) : TabbedBottomSheetDialog(activity) { val filters: Filter private val sort: Sort @@ -48,12 +48,12 @@ class LibrarySettingsSheet( val sheetScope = CoroutineScope(Job() + Dispatchers.IO) init { - filters = Filter(router.activity!!) - sort = Sort(router.activity!!) - display = Display(router.activity!!) + filters = Filter(activity) + sort = Sort(activity) + display = Display(activity) // SY --> - grouping = Grouping(router.activity!!) + grouping = Grouping(activity) // SY <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt similarity index 67% rename from app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreen.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt index c90ab24b8..ec3c4fa69 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt @@ -1,10 +1,12 @@ package eu.kanade.tachiyomi.ui.library import androidx.activity.compose.BackHandler +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.HelpOutline -import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable @@ -21,9 +23,11 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.util.fastAll import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow -import com.bluelinelabs.conductor.Router +import cafe.adriel.voyager.navigator.tab.LocalTabNavigator +import cafe.adriel.voyager.navigator.tab.TabOptions import eu.kanade.domain.UnsortedPreferences import eu.kanade.domain.category.model.Category import eu.kanade.domain.library.model.LibraryGroup @@ -44,33 +48,48 @@ import eu.kanade.presentation.library.components.SyncFavoritesConfirmDialog import eu.kanade.presentation.library.components.SyncFavoritesProgressDialog import eu.kanade.presentation.library.components.SyncFavoritesWarningDialog import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog -import eu.kanade.presentation.util.LocalRouter +import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.library.LibraryUpdateService -import eu.kanade.tachiyomi.ui.base.controller.pushController -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController -import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController -import eu.kanade.tachiyomi.ui.category.CategoryController +import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen +import eu.kanade.tachiyomi.ui.category.CategoryScreen +import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.system.toast -import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView import exh.favorites.FavoritesSyncStatus import exh.source.MERGED_SOURCE_ID -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -object LibraryScreen : Screen { +object LibraryTab : Tab { + + override val options: TabOptions + @Composable + get() { + val isSelected = LocalTabNavigator.current.current.key == key + val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_library_enter) + return TabOptions( + index = 0u, + title = stringResource(R.string.label_library), + icon = rememberAnimatedVectorPainter(image, isSelected), + ) + } + + override suspend fun onReselect(navigator: Navigator) { + requestOpenSettingsSheet() + } @Composable override fun Content() { - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val context = LocalContext.current val scope = rememberCoroutineScope() val haptic = LocalHapticFeedback.current @@ -127,7 +146,7 @@ object LibraryScreen : Screen { scope.launch { val randomItem = screenModel.getRandomLibraryItemForCurrentCategory() if (randomItem != null) { - router.openManga(randomItem.libraryManga.manga.id) + navigator.push(MangaScreen(randomItem.libraryManga.manga.id)) } else { snackbarHostState.showSnackbar(context.getString(R.string.information_no_entries_found)) } @@ -158,9 +177,9 @@ object LibraryScreen : Screen { .map { it.manga.id } screenModel.clearSelection() if (selectedMangaIds.isNotEmpty()) { - PreMigrationController.navigateToMigration( + PreMigrationScreen.navigateToMigration( Injekt.get().skipPreMigration().get(), - router, + navigator, selectedMangaIds, ) } else { @@ -172,66 +191,63 @@ object LibraryScreen : Screen { ) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, - contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets), ) { contentPadding -> - if (state.isLoading) { - LoadingScreen(modifier = Modifier.padding(contentPadding)) - return@Scaffold - } - - if (state.searchQuery.isNullOrEmpty() && state.libraryCount == 0) { - val handler = LocalUriHandler.current - EmptyScreen( - textResource = R.string.information_empty_library, - modifier = Modifier.padding(contentPadding), - actions = listOf( - EmptyScreenAction( - stringResId = R.string.getting_started_guide, - icon = Icons.Outlined.HelpOutline, - onClick = { handler.openUri("https://tachiyomi.org/help/guides/getting-started") }, + when { + state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) + state.searchQuery.isNullOrEmpty() && state.libraryCount == 0 -> { + val handler = LocalUriHandler.current + EmptyScreen( + textResource = R.string.information_empty_library, + modifier = Modifier.padding(contentPadding), + actions = listOf( + EmptyScreenAction( + stringResId = R.string.getting_started_guide, + icon = Icons.Outlined.HelpOutline, + onClick = { handler.openUri("https://tachiyomi.org/help/guides/getting-started") }, + ), ), - ), - ) - return@Scaffold + ) + } + else -> { + LibraryContent( + categories = state.categories, + searchQuery = state.searchQuery, + selection = state.selection, + contentPadding = contentPadding, + currentPage = { screenModel.activeCategory }, + isLibraryEmpty = state.libraryCount == 0, + showPageTabs = state.showCategoryTabs, + onChangeCurrentPage = { screenModel.activeCategory = it }, + onMangaClicked = { navigator.push(MangaScreen(it)) }, + onContinueReadingClicked = { it: LibraryManga -> + scope.launchIO { + val chapter = screenModel.getNextUnreadChapter(it.manga) + if (chapter != null) { + context.startActivity(ReaderActivity.newIntent(context, chapter.mangaId, chapter.id)) + } else { + snackbarHostState.showSnackbar(context.getString(R.string.no_next_chapter)) + } + } + Unit + }.takeIf { state.showMangaContinueButton }, + onToggleSelection = { screenModel.toggleSelection(it) }, + onToggleRangeSelection = { + screenModel.toggleRangeSelection(it) + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + }, + onRefresh = onClickRefresh, + onGlobalSearchClicked = { + navigator.push(GlobalSearchScreen(screenModel.state.value.searchQuery ?: "")) + }, + getNumberOfMangaForCategory = { state.getMangaCountForCategory(it) }, + getDisplayModeForPage = { state.categories[it].display }, + getColumnsForOrientation = { screenModel.getColumnsPreferenceForCurrentOrientation(it) }, + getLibraryForPage = { state.getLibraryItemsByPage(it) }, + isDownloadOnly = screenModel.isDownloadOnly, + isIncognitoMode = screenModel.isIncognitoMode, + ) + } } - - LibraryContent( - categories = state.categories, - searchQuery = state.searchQuery, - selection = state.selection, - contentPadding = contentPadding, - currentPage = { screenModel.activeCategory }, - isLibraryEmpty = state.libraryCount == 0, - showPageTabs = state.showCategoryTabs, - onChangeCurrentPage = { screenModel.activeCategory = it }, - onMangaClicked = { router.openManga(it) }, - onContinueReadingClicked = { it: LibraryManga -> - scope.launchIO { - val chapter = screenModel.getNextUnreadChapter(it.manga) - if (chapter != null) { - context.startActivity(ReaderActivity.newIntent(context, chapter.mangaId, chapter.id)) - } else { - snackbarHostState.showSnackbar(context.getString(R.string.no_next_chapter)) - } - } - Unit - }.takeIf { state.showMangaContinueButton }, - onToggleSelection = { screenModel.toggleSelection(it) }, - onToggleRangeSelection = { - screenModel.toggleRangeSelection(it) - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - }, - onRefresh = onClickRefresh, - onGlobalSearchClicked = { - router.pushController(GlobalSearchController(screenModel.state.value.searchQuery ?: "")) - }, - getNumberOfMangaForCategory = { state.getMangaCountForCategory(it) }, - getDisplayModeForPage = { state.categories[it].display }, - getColumnsForOrientation = { screenModel.getColumnsPreferenceForCurrentOrientation(it) }, - getLibraryForPage = { state.getLibraryItemsByPage(it) }, - isDownloadOnly = screenModel.isDownloadOnly, - isIncognitoMode = screenModel.isIncognitoMode, - ) } val onDismissRequest = screenModel::closeDialog @@ -242,7 +258,7 @@ object LibraryScreen : Screen { onDismissRequest = onDismissRequest, onEditCategories = { screenModel.clearSelection() - router.pushController(CategoryController()) + navigator.push(CategoryScreen()) }, onConfirm = { include, exclude -> screenModel.clearSelection() @@ -295,7 +311,7 @@ object LibraryScreen : Screen { SyncFavoritesProgressDialog( status = screenModel.favoritesSync.status.collectAsState().value, setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle(context) }, - openManga = { router.openManga(it.id) }, + openManga = { navigator.push(MangaScreen(it.id)) }, ) // SY <-- @@ -307,11 +323,9 @@ object LibraryScreen : Screen { } LaunchedEffect(state.selectionMode) { - // Could perhaps be removed when navigation is in a Compose world - if (router.backstackSize == 1) { - (context as? MainActivity)?.showBottomNav(!state.selectionMode) - } + HomeScreen.showBottomNav(!state.selectionMode) } + LaunchedEffect(state.isLoading) { if (!state.isLoading) { (context as? MainActivity)?.ready = true @@ -319,23 +333,19 @@ object LibraryScreen : Screen { } LaunchedEffect(Unit) { - launch { queryEvent.collectLatest(screenModel::search) } - launch { requestSettingsSheetEvent.collectLatest { onClickFilter() } } + launch { queryEvent.receiveAsFlow().collect(screenModel::search) } + launch { requestSettingsSheetEvent.receiveAsFlow().collectLatest { onClickFilter() } } } } - private fun Router.openManga(mangaId: Long) { - pushController(MangaController(mangaId)) - } - // For invoking search from other screen - private val queryEvent = MutableSharedFlow(replay = 1) - fun search(query: String) = queryEvent.tryEmit(query) + private val queryEvent = Channel() + suspend fun search(query: String) = queryEvent.send(query) // For opening settings sheet in LibraryController - private val requestSettingsSheetEvent = MutableSharedFlow() - private val openSettingsSheetEvent_ = MutableSharedFlow() - val openSettingsSheetEvent = openSettingsSheetEvent_.asSharedFlow() - private suspend fun sendSettingsSheetIntent(category: Category) = openSettingsSheetEvent_.emit(category) - suspend fun requestOpenSettingsSheet() = requestSettingsSheetEvent.emit(Unit) + private val requestSettingsSheetEvent = Channel() + private val openSettingsSheetEvent_ = Channel() + val openSettingsSheetEvent = openSettingsSheetEvent_.receiveAsFlow() + private suspend fun sendSettingsSheetIntent(category: Category) = openSettingsSheetEvent_.send(category) + suspend fun requestOpenSettingsSheet() = requestSettingsSheetEvent.send(Unit) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index edf7c6804..17036be0d 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -7,80 +7,75 @@ import android.graphics.Color import android.os.Build import android.os.Bundle import android.os.Looper -import android.view.Menu -import android.view.ViewGroup +import android.view.View import android.view.Window import android.widget.Toast -import androidx.appcompat.view.ActionMode +import androidx.activity.compose.BackHandler +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.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext import androidx.core.animation.doOnEnd -import androidx.core.graphics.ColorUtils import androidx.core.splashscreen.SplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat -import androidx.core.view.isVisible import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.interpolator.view.animation.LinearOutSlowInInterpolator import androidx.lifecycle.lifecycleScope -import com.bluelinelabs.conductor.Conductor -import com.bluelinelabs.conductor.Controller -import com.bluelinelabs.conductor.ControllerChangeHandler -import com.bluelinelabs.conductor.Router -import com.bluelinelabs.conductor.RouterTransaction -import com.google.android.material.navigation.NavigationBarView +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior +import cafe.adriel.voyager.navigator.currentOrThrow +import cafe.adriel.voyager.transitions.ScreenTransition import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback -import dev.chrisbanes.insetter.applyInsetter import eu.kanade.domain.UnsortedPreferences import eu.kanade.domain.base.BasePreferences +import eu.kanade.domain.category.model.Category import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.ui.UiPreferences +import eu.kanade.presentation.more.settings.screen.ConfigureExhDialog +import eu.kanade.presentation.more.settings.screen.WhatsNewDialog +import eu.kanade.presentation.util.Transition +import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.updater.AppUpdateChecker import eu.kanade.tachiyomi.data.updater.AppUpdateResult -import eu.kanade.tachiyomi.databinding.MainActivityBinding -import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.ui.base.activity.BaseActivity -import eu.kanade.tachiyomi.ui.base.controller.ComposeContentController -import eu.kanade.tachiyomi.ui.base.controller.DialogController -import eu.kanade.tachiyomi.ui.base.controller.RootController -import eu.kanade.tachiyomi.ui.base.controller.pushController -import eu.kanade.tachiyomi.ui.base.controller.setRoot -import eu.kanade.tachiyomi.ui.browse.BrowseController -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController -import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController -import eu.kanade.tachiyomi.ui.download.DownloadController -import eu.kanade.tachiyomi.ui.history.HistoryController -import eu.kanade.tachiyomi.ui.library.LibraryController -import eu.kanade.tachiyomi.ui.manga.MangaController -import eu.kanade.tachiyomi.ui.more.MoreController -import eu.kanade.tachiyomi.ui.more.NewUpdateDialogController -import eu.kanade.tachiyomi.ui.setting.SettingsMainController -import eu.kanade.tachiyomi.ui.updates.UpdatesController -import eu.kanade.tachiyomi.util.lang.launchIO -import eu.kanade.tachiyomi.util.lang.launchUI -import eu.kanade.tachiyomi.util.preference.asHotFlow +import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen +import eu.kanade.tachiyomi.ui.home.HomeScreen +import eu.kanade.tachiyomi.ui.library.LibrarySettingsSheet +import eu.kanade.tachiyomi.ui.library.LibraryTab +import eu.kanade.tachiyomi.ui.manga.MangaScreen +import eu.kanade.tachiyomi.ui.more.NewUpdateScreen +import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.util.system.dpToPx -import eu.kanade.tachiyomi.util.system.getThemeColor -import eu.kanade.tachiyomi.util.system.isTabletUi import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.toast +import eu.kanade.tachiyomi.util.view.setComposeContent import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat import exh.EXHMigrations import exh.eh.EHentaiUpdateWorker import exh.source.BlacklistedSources import exh.source.EH_SOURCE_ID import exh.source.EXH_SOURCE_ID -import exh.uconfig.WarnConfigureDialogController +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import logcat.LogPriority import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -99,24 +94,20 @@ class MainActivity : BaseActivity() { private val unsortedPreferences: UnsortedPreferences by injectLazy() // SY <-- - lateinit var binding: MainActivityBinding - - private lateinit var router: Router - - private val startScreenId = R.id.nav_library - private var isConfirmingExit: Boolean = false private var isHandlingShortcut: Boolean = false - /** - * App bar lift state for backstack - */ - private val backstackLiftState = mutableMapOf() - private val chapterCache: ChapterCache by injectLazy() // To be checked by splash screen. If true then splash screen will be removed. var ready = false + /** + * Sheet containing filter/sort/display items. + */ + private var settingsSheet: LibrarySettingsSheet? = null + + private lateinit var navigator: Navigator + // SY --> // Idle-until-urgent private var firstPaint = false @@ -134,6 +125,8 @@ class MainActivity : BaseActivity() { iuuQueue += task } } + + private var runExhConfigureDialog by mutableStateOf(false) // SY <-- override fun onCreate(savedInstanceState: Bundle?) { @@ -164,23 +157,64 @@ class MainActivity : BaseActivity() { false } - binding = MainActivityBinding.inflate(layoutInflater) - // Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079 if (!isTaskRoot) { finish() return } - setContentView(binding.root) - setSupportActionBar(binding.toolbar) - // Draw edge-to-edge WindowCompat.setDecorFitsSystemWindows(window, false) - binding.bottomNav?.applyInsetter { - type(navigationBars = true) { - padding() + + settingsSheet = LibrarySettingsSheet(this) + LibraryTab.openSettingsSheetEvent + .onEach(::showSettingsSheet) + .launchIn(lifecycleScope) + + setComposeContent { + Navigator( + screen = HomeScreen, + disposeBehavior = NavigatorDisposeBehavior(disposeNestedNavigators = false, disposeSteps = true), + ) { navigator -> + if (navigator.size == 1) { + ConfirmExit() + } + + // Shows current screen + ScreenTransition(navigator = navigator, transition = { Transition.OneWayFade }) + + // Pop source-related screens when incognito mode is turned off + LaunchedEffect(Unit) { + preferences.incognitoMode().changes() + .drop(1) + .onEach { + if (!it) { + val currentScreen = navigator.lastItem + if (currentScreen is BrowseSourceScreen || + (currentScreen is MangaScreen && currentScreen.fromSource) + ) { + navigator.popUntilRoot() + } + } + } + .launchIn(this) + } + + LaunchedEffect(navigator) { + this@MainActivity.navigator = navigator + } + + CheckForUpdate() } + + var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) } + if (showChangelog) { + // SY --> + WhatsNewDialog(onDismissRequest = { showChangelog = false }) + // SY <-- + } + + ConfigureExhDialog(run = runExhConfigureDialog, onRunning = { runExhConfigureDialog = false }) } val startTime = System.currentTimeMillis() @@ -190,114 +224,26 @@ class MainActivity : BaseActivity() { } setSplashScreenExitAnimation(splashScreen) - nav.setOnItemSelectedListener { item -> - val id = item.itemId - - val currentRoot = router.backstack.firstOrNull() - if (currentRoot?.tag()?.toIntOrNull() != id) { - when (id) { - R.id.nav_library -> router.setRoot(LibraryController(), id) - R.id.nav_updates -> router.setRoot(UpdatesController(), id) - R.id.nav_history -> router.setRoot(HistoryController(), id) - R.id.nav_browse -> router.setRoot(BrowseController(toExtensions = false), id) - R.id.nav_more -> router.setRoot(MoreController(), id) - } - } else if (!isHandlingShortcut) { - when (id) { - R.id.nav_library -> { - val controller = router.getControllerWithTag(id.toString()) as? LibraryController - controller?.showSettingsSheet() - } - R.id.nav_updates -> { - if (router.backstackSize == 1) { - router.pushController(DownloadController()) - } - } - R.id.nav_history -> { - if (router.backstackSize == 1) { - try { - val historyController = router.backstack[0].controller as HistoryController - historyController.resumeLastChapterRead() - } catch (e: Exception) { - toast(R.string.cant_open_last_read_chapter) - } - } - } - R.id.nav_more -> { - if (router.backstackSize == 1) { - router.pushController(SettingsMainController()) - } - } - } - } - true - } - - val container: ViewGroup = binding.controllerContainer - router = Conductor.attachRouter(this, container, savedInstanceState) - .setPopRootControllerMode(Router.PopRootControllerMode.NEVER) - router.addChangeListener( - object : ControllerChangeHandler.ControllerChangeListener { - override fun onChangeStarted( - to: Controller?, - from: Controller?, - isPush: Boolean, - container: ViewGroup, - handler: ControllerChangeHandler, - ) { - syncActivityViewWithController(to, from, isPush) - } - - override fun onChangeCompleted( - to: Controller?, - from: Controller?, - isPush: Boolean, - container: ViewGroup, - handler: ControllerChangeHandler, - ) { - } - }, - ) - if (!router.hasRootController()) { - // Set start screen - if (!handleIntentAction(intent)) { - moveToStartScreen() - } - } - syncActivityViewWithController() - - binding.toolbar.setNavigationOnClickListener { - onBackPressed() - } - if (savedInstanceState == null) { + // Set start screen + lifecycleScope.launch { handleIntentAction(intent) } + // Reset Incognito Mode on relaunch preferences.incognitoMode().set(false) - // Show changelog prompt on update - if (didMigration) { - WhatsNewDialogController().showDialog(router) - } - // EXH <-- - // SY --> initWhenIdle { // Upload settings if (unsortedPreferences.enableExhentai().get() && unsortedPreferences.exhShowSettingsUploadWarning().get() ) { - WarnConfigureDialogController.uploadSettings(router) + runExhConfigureDialog = true } // Scheduler uploader job if required EHentaiUpdateWorker.scheduleBackground(this) } // SY <-- - } else { - // Restore selected nav item - router.backstack.firstOrNull()?.tag()?.toIntOrNull()?.let { - nav.menu.findItem(it).isChecked = true - } } // SY --> if (!unsortedPreferences.isHentaiEnabled().get()) { @@ -305,40 +251,55 @@ class MainActivity : BaseActivity() { BlacklistedSources.HIDDEN_SOURCES += EXH_SOURCE_ID } // SY --> + } - merge(libraryPreferences.showUpdatesNavBadge().changes(), libraryPreferences.unreadUpdatesCount().changes()) - .onEach { setUnreadUpdatesBadge() } - .launchIn(lifecycleScope) + private fun showSettingsSheet(category: Category? = null) { + if (category != null) { + settingsSheet?.show(category) + } else { + lifecycleScope.launch { LibraryTab.requestOpenSettingsSheet() } + } + } - sourcePreferences.extensionUpdatesCount() - .asHotFlow { setExtensionsBadge() } - .launchIn(lifecycleScope) + @Composable + private fun ConfirmExit() { + val scope = rememberCoroutineScope() + val confirmExit by preferences.confirmExit().collectAsState() + var waitingConfirmation by remember { mutableStateOf(false) } + BackHandler(enabled = !waitingConfirmation && confirmExit) { + scope.launch { + waitingConfirmation = true + val toast = toast(R.string.confirm_exit, Toast.LENGTH_LONG) + delay(2.seconds) + toast.cancel() + waitingConfirmation = false + } + } + } - preferences.downloadedOnly() - .asHotFlow { binding.downloadedOnly.isVisible = it } - .launchIn(lifecycleScope) - - binding.incognitoMode.isVisible = preferences.incognitoMode().get() - preferences.incognitoMode().changes() - .drop(1) - .onEach { - binding.incognitoMode.isVisible = it - - // Close BrowseSourceController and its MangaController child when incognito mode is disabled - if (!it) { - val fg = router.backstack.lastOrNull()?.controller - if (fg is BrowseSourceController || fg is MangaController && fg.fromSource) { - router.popToRoot() + @Composable + private fun CheckForUpdate() { + val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow + LaunchedEffect(Unit) { + // App updates + if (BuildConfig.INCLUDE_UPDATER) { + try { + val result = AppUpdateChecker().checkForUpdate(context) + if (result is AppUpdateResult.NewUpdate) { + val updateScreen = NewUpdateScreen( + versionName = result.release.version, + changelogInfo = result.release.info, + releaseLink = result.release.releaseLink, + downloadLink = result.release.getDownloadLink(), + ) + navigator.push(updateScreen) } + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) } } - .launchIn(lifecycleScope) - - // SY --> - uiPreferences.bottomBarLabels() - .asHotFlow { setNavLabelVisibility() } - .launchIn(lifecycleScope) - // SY <-- + } } /** @@ -348,16 +309,16 @@ class MainActivity : BaseActivity() { * after the animation is finished. */ private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) { + val root = findViewById(android.R.id.content) val setNavbarScrim = { // Make sure navigation bar is on bottom before we modify it - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> + ViewCompat.setOnApplyWindowInsetsListener(root) { _, insets -> if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) { - val elevation = binding.bottomNav?.elevation ?: 0F - window.setNavigationBarTransparentCompat(this@MainActivity, elevation) + window.setNavigationBarTransparentCompat(this@MainActivity, 3.dpToPx.toFloat()) } insets } - ViewCompat.requestApplyInsets(binding.root) + ViewCompat.requestApplyInsets(root) } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && splashScreen != null) { @@ -375,7 +336,7 @@ class MainActivity : BaseActivity() { duration = SPLASH_EXIT_ANIM_DURATION addUpdateListener { va -> val value = va.animatedValue as Float - binding.root.translationY = value * 16.dpToPx + root.translationY = value * 16.dpToPx } } @@ -403,69 +364,13 @@ class MainActivity : BaseActivity() { } override fun onNewIntent(intent: Intent) { - if (!handleIntentAction(intent)) { + val handle = runBlocking { handleIntentAction(intent) } + if (!handle) { super.onNewIntent(intent) } } - override fun onResume() { - super.onResume() - checkForUpdates() - } - - private fun checkForUpdates() { - lifecycleScope.launchIO { - // App updates - if (BuildConfig.INCLUDE_UPDATER) { - try { - val result = AppUpdateChecker().checkForUpdate(this@MainActivity) - if (result is AppUpdateResult.NewUpdate) { - NewUpdateDialogController(result).showDialog(router) - } - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - } - } - - // Extension updates - try { - ExtensionGithubApi().checkForUpdates( - this@MainActivity, - fromAvailableExtensionList = true, - )?.let { pendingUpdates -> - sourcePreferences.extensionUpdatesCount().set(pendingUpdates.size) - } - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - } - } - } - - private fun setUnreadUpdatesBadge() { - val updates = if (libraryPreferences.showUpdatesNavBadge().get()) libraryPreferences.unreadUpdatesCount().get() else 0 - if (updates > 0) { - nav.getOrCreateBadge(R.id.nav_updates).apply { - number = updates - setContentDescriptionQuantityStringsResource(R.plurals.notification_chapters_generic) - } - } else { - nav.removeBadge(R.id.nav_updates) - } - } - - private fun setExtensionsBadge() { - val updates = sourcePreferences.extensionUpdatesCount().get() - if (updates > 0) { - nav.getOrCreateBadge(R.id.nav_browse).apply { - number = updates - setContentDescriptionQuantityStringsResource(R.plurals.update_check_notification_ext_updates) - } - } else { - nav.removeBadge(R.id.nav_browse) - } - } - - private fun handleIntentAction(intent: Intent): Boolean { + private suspend fun handleIntentAction(intent: Intent): Boolean { val notificationId = intent.getIntExtra("notificationId", -1) if (notificationId > -1) { NotificationReceiver.dismissNotification(applicationContext, notificationId, intent.getIntExtra("groupId", 0)) @@ -474,32 +379,19 @@ class MainActivity : BaseActivity() { isHandlingShortcut = true when (intent.action) { - SHORTCUT_LIBRARY -> setSelectedNavItem(R.id.nav_library) - SHORTCUT_RECENTLY_UPDATED -> setSelectedNavItem(R.id.nav_updates) - SHORTCUT_RECENTLY_READ -> setSelectedNavItem(R.id.nav_history) - SHORTCUT_CATALOGUES -> setSelectedNavItem(R.id.nav_browse) - SHORTCUT_EXTENSIONS -> { - if (router.backstackSize > 1) { - router.popToRoot() - } - setSelectedNavItem(R.id.nav_browse) - router.pushController(BrowseController(toExtensions = true)) - } + SHORTCUT_LIBRARY -> HomeScreen.openTab(HomeScreen.Tab.Library()) + SHORTCUT_RECENTLY_UPDATED -> HomeScreen.openTab(HomeScreen.Tab.Updates) + SHORTCUT_RECENTLY_READ -> HomeScreen.openTab(HomeScreen.Tab.History) + SHORTCUT_CATALOGUES -> HomeScreen.openTab(HomeScreen.Tab.Browse(false)) + SHORTCUT_EXTENSIONS -> HomeScreen.openTab(HomeScreen.Tab.Browse(true)) SHORTCUT_MANGA -> { - val extras = intent.extras ?: return false - val fgController = router.backstack.lastOrNull()?.controller as? MangaController - if (fgController?.mangaId != extras.getLong(MangaController.MANGA_EXTRA)) { - router.popToRoot() - setSelectedNavItem(R.id.nav_library) - router.pushController(RouterTransaction.with(MangaController(extras))) - } + val idToOpen = intent.extras?.getLong(Constants.MANGA_EXTRA) ?: return false + navigator.popUntilRoot() + HomeScreen.openTab(HomeScreen.Tab.Library(idToOpen)) } SHORTCUT_DOWNLOADS -> { - if (router.backstackSize > 1) { - router.popToRoot() - } - setSelectedNavItem(R.id.nav_more) - router.pushController(DownloadController()) + navigator.popUntilRoot() + HomeScreen.openTab(HomeScreen.Tab.More(toDownloads = true)) } Intent.ACTION_SEARCH, Intent.ACTION_SEND, "com.google.android.gms.actions.SEARCH_ACTION" -> { // If the intent match the "standard" Android search intent @@ -508,20 +400,16 @@ class MainActivity : BaseActivity() { // Get the search query provided in extras, and if not null, perform a global search with it. val query = intent.getStringExtra(SearchManager.QUERY) ?: intent.getStringExtra(Intent.EXTRA_TEXT) if (query != null && query.isNotEmpty()) { - if (router.backstackSize > 1) { - router.popToRoot() - } - router.pushController(GlobalSearchController(query)) + navigator.popUntilRoot() + navigator.push(GlobalSearchScreen(query)) } } INTENT_SEARCH -> { val query = intent.getStringExtra(INTENT_SEARCH_QUERY) if (query != null && query.isNotEmpty()) { - val filter = intent.getStringExtra(INTENT_SEARCH_FILTER) - if (router.backstackSize > 1) { - router.popToRoot() - } - router.pushController(GlobalSearchController(query, filter ?: "")) + val filter = intent.getStringExtra(INTENT_SEARCH_FILTER) ?: "" + navigator.popUntilRoot() + navigator.push(GlobalSearchScreen(query, filter)) } } else -> { @@ -535,190 +423,22 @@ class MainActivity : BaseActivity() { return true } - @Suppress("UNNECESSARY_SAFE_CALL") override fun onDestroy() { + settingsSheet?.sheetScope?.cancel() + settingsSheet = null super.onDestroy() - - // Binding sometimes isn't actually instantiated yet somehow - nav?.setOnItemSelectedListener(null) - binding?.toolbar?.setNavigationOnClickListener(null) } override fun onBackPressed() { - if (router.handleBack()) { - // A Router is consuming back press - return - } - val backstackSize = router.backstackSize - val startScreen = router.getControllerWithTag("$startScreenId") - if (backstackSize == 1 && startScreen == null) { - // Return to start screen - moveToStartScreen() - } else if (shouldHandleExitConfirmation()) { - // Exit confirmation (resets after 2 seconds) - lifecycleScope.launchUI { resetExitConfirmation() } - } else if (backstackSize == 1) { - // Regular back (i.e. closing the app) - if (libraryPreferences.autoClearChapterCache().get()) { - chapterCache.clear() - } - super.onBackPressed() + if (navigator.size == 1 && + !onBackPressedDispatcher.hasEnabledCallbacks() && + libraryPreferences.autoClearChapterCache().get() + ) { + chapterCache.clear() } + super.onBackPressed() } - fun moveToStartScreen() { - setSelectedNavItem(startScreenId) - } - - override fun onSupportActionModeStarted(mode: ActionMode) { - binding.appbar.apply { - tag = isTransparentWhenNotLifted - isTransparentWhenNotLifted = false - } - // Color taken from m3_appbar_background - window.statusBarColor = ColorUtils.compositeColors( - getColor(R.color.m3_appbar_overlay_color), - getThemeColor(R.attr.colorSurface), - ) - super.onSupportActionModeStarted(mode) - } - - override fun onSupportActionModeFinished(mode: ActionMode) { - binding.appbar.apply { - isTransparentWhenNotLifted = (tag as? Boolean) ?: false - tag = null - } - window.statusBarColor = getThemeColor(android.R.attr.statusBarColor) - super.onSupportActionModeFinished(mode) - } - - private suspend fun resetExitConfirmation() { - isConfirmingExit = true - val toast = toast(R.string.confirm_exit, Toast.LENGTH_LONG) - delay(2.seconds) - toast.cancel() - isConfirmingExit = false - } - - private fun shouldHandleExitConfirmation(): Boolean { - return router.backstackSize == 1 && - router.getControllerWithTag("$startScreenId") != null && - preferences.confirmExit().get() && - !isConfirmingExit - } - - fun setSelectedNavItem(itemId: Int) { - if (!isFinishing) { - nav.selectedItemId = itemId - } - } - - private fun syncActivityViewWithController( - to: Controller? = null, - from: Controller? = null, - isPush: Boolean = true, - ) { - var internalTo = to - - if (internalTo == null) { - // Should go here when the activity is recreated and dialog controller is on top of the backstack - // Then we'll assume the top controller is the parent controller of this dialog - val backstack = router.backstack - internalTo = backstack.lastOrNull()?.controller - if (internalTo is DialogController) { - internalTo = backstack.getOrNull(backstack.size - 2)?.controller ?: return - } - } else { - // Ignore changes for normal transactions - if (from is DialogController || internalTo is DialogController) { - return - } - } - - supportActionBar?.setDisplayHomeAsUpEnabled(router.backstackSize != 1) - - // Always show appbar again when changing controllers - binding.appbar.setExpanded(true) - - if ((from == null || from is RootController) && internalTo !is RootController) { - showNav(false) - } - if (internalTo is RootController) { - // Always show bottom nav again when returning to a RootController - showNav(true) - } - - val isComposeController = internalTo is ComposeContentController - binding.appbar.isVisible = !isComposeController - binding.controllerContainer.enableScrollingBehavior(!isComposeController) - - if (!isTabletUi()) { - // Save lift state - if (isPush) { - if (router.backstackSize > 1) { - // Save lift state - from?.let { - backstackLiftState[it.instanceId] = binding.appbar.isLifted - } - } else { - backstackLiftState.clear() - } - binding.appbar.isLifted = false - } else { - internalTo?.let { - binding.appbar.isLifted = backstackLiftState.getOrElse(it.instanceId) { false } - } - from?.let { - backstackLiftState.remove(it.instanceId) - } - } - } - } - - private fun showNav(visible: Boolean) { - showBottomNav(visible) - showSideNav(visible) - } - - // Also used from some controllers to swap bottom nav with action toolbar - fun showBottomNav(visible: Boolean) { - if (visible) { - binding.bottomNav?.slideUp() - // SY --> - binding.bottomNav?.menu?.let { updateNavMenu(it) } - // SY <-- - } else { - binding.bottomNav?.slideDown() - } - } - - private fun showSideNav(visible: Boolean) { - binding.sideNav?.isVisible = visible - // SY --> - binding.sideNav?.let { updateNavMenu(it.menu) } - // SY <-- - } - - // SY --> - private fun updateNavMenu(menu: Menu) { - menu.findItem(R.id.nav_updates).isVisible = uiPreferences.showNavUpdates().get() - menu.findItem(R.id.nav_history).isVisible = uiPreferences.showNavHistory().get() - } - // SY <-- - - private val nav: NavigationBarView - get() = binding.bottomNav ?: binding.sideNav!! - - // SY --> - private fun setNavLabelVisibility() { - if (uiPreferences.bottomBarLabels().get()) { - nav.labelVisibilityMode = NavigationBarView.LABEL_VISIBILITY_LABELED - } else { - nav.labelVisibilityMode = NavigationBarView.LABEL_VISIBILITY_SELECTED - } - } - // SY <-- - init { registerSecureActivity(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/WhatsNewDialogController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/WhatsNewDialogController.kt deleted file mode 100644 index 3ea8f355b..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/WhatsNewDialogController.kt +++ /dev/null @@ -1,34 +0,0 @@ -package eu.kanade.tachiyomi.ui.main - -import android.app.Dialog -import android.content.Context -import android.os.Bundle -import android.util.AttributeSet -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import eu.kanade.tachiyomi.BuildConfig -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.DialogController -import eu.kanade.tachiyomi.util.system.isPreviewBuildType -import it.gmariotti.changelibs.library.view.ChangeLogRecyclerView - -class WhatsNewDialogController : DialogController() { - - @Suppress("DEPRECATION") - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val activity = activity!! - val view = WhatsNewRecyclerView(activity) - return MaterialAlertDialogBuilder(activity) - .setTitle(R.string.whats_new) - .setView(view) - .setPositiveButton(android.R.string.cancel, null) - .create() - } - - class WhatsNewRecyclerView(context: Context) : ChangeLogRecyclerView(context) { - override fun initAttrs(attrs: AttributeSet?, defStyle: Int) { - mRowLayoutId = R.layout.changelog_row_layout - mRowHeaderLayoutId = R.layout.changelog_header_layout - mChangeLogFileResourceId = if (BuildConfig.DEBUG /* SY --> */ || isPreviewBuildType/* SY <-- */) R.raw.changelog_debug else R.raw.changelog_release - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt deleted file mode 100644 index e680b6d37..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ /dev/null @@ -1,53 +0,0 @@ -package eu.kanade.tachiyomi.ui.manga - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen -import eu.kanade.tachiyomi.util.system.getSerializableCompat - -class MangaController : BasicFullComposeController { - - @Suppress("unused") - constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA)) - - constructor( - mangaId: Long, - fromSource: Boolean = false, - smartSearchConfig: SourcesScreen.SmartSearchConfig? = null, - ) : super(bundleOf(MANGA_EXTRA to mangaId, FROM_SOURCE_EXTRA to fromSource, SMART_SEARCH_CONFIG_EXTRA to smartSearchConfig)) - - // SY --> - constructor(redirect: MangaInfoScreenModel.EXHRedirect) : super( - bundleOf(MANGA_EXTRA to redirect.mangaId), - ) - // SY <-- - - val mangaId: Long - get() = args.getLong(MANGA_EXTRA) - - val fromSource: Boolean - get() = args.getBoolean(FROM_SOURCE_EXTRA) - - // SY --> - val smartSearchConfig: SourcesScreen.SmartSearchConfig? - get() = args.getSerializableCompat(SMART_SEARCH_CONFIG_EXTRA) - // SY <-- - - @Composable - override fun ComposeContent() { - Navigator(screen = MangaScreen(mangaId, fromSource, smartSearchConfig)) - } - - companion object { - const val FROM_SOURCE_EXTRA = "from_source" - const val MANGA_EXTRA = "manga" - - // EXH --> - const val UPDATE_EXTRA = "update" - const val SMART_SEARCH_CONFIG_EXTRA = "smartSearchConfig" - // EXH <-- - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt index 2e4c3735a..e1f1bbee6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType @@ -29,7 +30,6 @@ import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.transitions.ScreenTransition -import com.bluelinelabs.conductor.Router import com.google.android.material.dialog.MaterialAlertDialogBuilder import eu.kanade.domain.UnsortedPreferences import eu.kanade.domain.chapter.model.Chapter @@ -47,7 +47,6 @@ import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog import eu.kanade.presentation.manga.components.MangaCoverDialog import eu.kanade.presentation.manga.components.SelectScanlatorsDialog import eu.kanade.presentation.util.LocalNavigatorContentPadding -import eu.kanade.presentation.util.LocalRouter import eu.kanade.presentation.util.isTabletUi import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.CatalogueSource @@ -55,22 +54,16 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.isLocalOrStub import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.ui.base.controller.pushController -import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController -import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedController +import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedScreen -import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen import eu.kanade.tachiyomi.ui.category.CategoryScreen -import eu.kanade.tachiyomi.ui.history.HistoryController -import eu.kanade.tachiyomi.ui.library.LibraryController -import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.manga.merged.EditMergedSettingsDialog import eu.kanade.tachiyomi.ui.manga.track.TrackInfoDialogHomeScreen import eu.kanade.tachiyomi.ui.reader.ReaderActivity -import eu.kanade.tachiyomi.ui.updates.UpdatesController import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.util.chapter.getNextUnread import eu.kanade.tachiyomi.util.lang.launchUI @@ -78,9 +71,9 @@ import eu.kanade.tachiyomi.util.lang.withNonCancellableContext import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toast -import exh.md.similar.MangaDexSimilarController -import exh.pagepreview.PagePreviewController -import exh.recs.RecommendsController +import exh.md.similar.MangaDexSimilarScreen +import exh.pagepreview.PagePreviewScreen +import exh.recs.RecommendsScreen import exh.source.MERGED_SOURCE_ID import exh.source.getMainSource import exh.source.isMdBasedSource @@ -89,12 +82,13 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.take +import kotlinx.coroutines.launch import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class MangaScreen( private val mangaId: Long, - private val fromSource: Boolean = false, + val fromSource: Boolean = false, private val smartSearchConfig: SourcesScreen.SmartSearchConfig? = null, ) : Screen { @@ -103,9 +97,9 @@ class MangaScreen( @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow - val router = LocalRouter.currentOrThrow val context = LocalContext.current val haptic = LocalHapticFeedback.current + val scope = rememberCoroutineScope() val screenModel = rememberScreenModel { MangaInfoScreenModel(context, mangaId, fromSource, smartSearchConfig != null) } val state by screenModel.state.collectAsState() @@ -123,8 +117,8 @@ class MangaScreen( screenModel.redirectFlow .take(1) .onEach { - router.replaceTopController( - MangaController(it).withFadeTransaction(), + navigator.replace( + MangaScreen(it.mangaId), ) } .launchIn(this) @@ -135,12 +129,7 @@ class MangaScreen( state = successState, snackbarHostState = screenModel.snackbarHostState, isTabletUi = isTabletUi(), - onBackClicked = { - when { - navigator.canPop -> navigator.pop() - else -> router.popCurrentController() - } - }, + onBackClicked = navigator::pop, onChapterClicked = { openChapter(context, it) }, onDownloadChapter = screenModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() }, onAddToLibraryClicked = { @@ -158,11 +147,11 @@ class MangaScreen( // SY <-- onWebViewLongClicked = { copyMangaUrl(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource }, onTrackingClicked = screenModel::showTrackDialog.takeIf { successState.trackingAvailable }, - onTagClicked = { performGenreSearch(router, navigator, it, screenModel.source!!) }, + onTagClicked = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } }, onFilterButtonClicked = screenModel::showSettingsDialog, onRefresh = screenModel::fetchAllFromSource, onContinueReading = { continueReading(context, screenModel.getNextUnreadChapter()) }, - onSearch = { query, global -> performSearch(router, navigator, query, global) }, + onSearch = { query, global -> scope.launch { performSearch(navigator, query, global) } }, onCoverClicked = screenModel::showCoverDialog, onShareClicked = { shareManga(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource }, onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() }, @@ -171,12 +160,12 @@ class MangaScreen( onMigrateClicked = { migrateManga(navigator, screenModel.manga!!) }.takeIf { successState.manga.favorite }, onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) }, onEditInfoClicked = screenModel::showEditMangaInfoDialog, - onRecommendClicked = { openRecommends(context, router, screenModel.source?.getMainSource(), successState.manga) }, + onRecommendClicked = { openRecommends(context, navigator, screenModel.source?.getMainSource(), successState.manga) }, onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog, onMergeClicked = { openSmartSearch(navigator, successState.manga) }, onMergeWithAnotherClicked = { mergeWithAnother(navigator, context, successState.manga, screenModel::smartSearchMerge) }, onOpenPagePreview = { openPagePreview(context, successState.chapters.getNextUnread(successState.manga), it) }, - onMorePreviewsClicked = { openMorePagePreviews(router, successState.manga) }, + onMorePreviewsClicked = { openMorePagePreviews(navigator, successState.manga) }, // SY <-- onMultiBookmarkClicked = screenModel::bookmarkChapters, onMultiMarkAsReadClicked = screenModel::markChaptersRead, @@ -366,52 +355,29 @@ class MangaScreen( * * @param query the search query to the parent controller */ - private fun performSearch(router: Router, navigator: Navigator, query: String, global: Boolean) { + private suspend fun performSearch(navigator: Navigator, query: String, global: Boolean) { if (global) { - router.pushController(GlobalSearchController(query)) + navigator.push(GlobalSearchScreen(query)) return } - // SY --> - if (navigator.canPop) { - when (val previousScreen = navigator.items[navigator.items.size - 2]) { - is SourceFeedScreen -> { - navigator.pop() - previousScreen.onBrowseClick(router, previousScreen.sourceId, query) - } - } - - return - } - // SY <-- - - if (router.backstackSize < 2) { + if (navigator.size < 2) { return } - when (val previousController = router.backstack[router.backstackSize - 2].controller) { - is LibraryController -> { - router.handleBack() + when (val previousController = navigator.items[navigator.size - 2]) { + is HomeScreen -> { + navigator.pop() previousController.search(query) } - is UpdatesController, - is HistoryController, - -> { - // Manually navigate to LibraryController - router.handleBack() - (router.activity as MainActivity).setSelectedNavItem(R.id.nav_library) - val controller = router.getControllerWithTag(R.id.nav_library.toString()) as LibraryController - controller.search(query) - } - is BrowseSourceController -> { - router.handleBack() - previousController.searchWithQuery(query) + is BrowseSourceScreen -> { + navigator.pop() + previousController.search(query) } // SY --> - is SourceFeedController -> { - router.handleBack() - router.handleBack() - router.pushController(BrowseSourceController(previousController.sourceId, query)) + is SourceFeedScreen -> { + navigator.pop() + navigator.replace(BrowseSourceScreen(previousController.sourceId, query)) } // SY <-- } @@ -422,20 +388,17 @@ class MangaScreen( * * @param genreName the search genre to the parent controller */ - private fun performGenreSearch(router: Router, navigator: Navigator, genreName: String, source: Source) { - if (router.backstackSize < 2) { + private suspend fun performGenreSearch(navigator: Navigator, genreName: String, source: Source) { + if (navigator.size < 2) { return } - val previousController = router.backstack[router.backstackSize - 2].controller - - if (previousController is BrowseSourceController && - source is HttpSource - ) { - router.handleBack() - previousController.searchWithGenre(genreName) + val previousController = navigator.items[navigator.size - 2] + if (previousController is BrowseSourceScreen && source is HttpSource) { + navigator.pop() + previousController.searchGenre(genreName) } else { - performSearch(router, navigator, genreName, global = false) + performSearch(navigator, genreName, global = false) } } @@ -484,8 +447,8 @@ class MangaScreen( .show() } - private fun openMorePagePreviews(router: Router, manga: Manga) { - router.pushController(PagePreviewController(manga.id)) + private fun openMorePagePreviews(navigator: Navigator, manga: Manga) { + navigator.push(PagePreviewScreen(manga.id)) } private fun openPagePreview(context: Context, chapter: Chapter?, page: Int) { @@ -527,7 +490,7 @@ class MangaScreen( // EXH <-- // AZ --> - private fun openRecommends(context: Context, router: Router, source: Source?, manga: Manga) { + private fun openRecommends(context: Context, navigator: Navigator, source: Source?, manga: Manga) { source ?: return if (source.isMdBasedSource()) { MaterialAlertDialogBuilder(context) @@ -541,13 +504,13 @@ class MangaScreen( ) { dialog, index -> dialog.dismiss() when (index) { - 0 -> router.pushController(MangaDexSimilarController(manga, source as CatalogueSource)) - 1 -> router.pushController(RecommendsController(manga, source as CatalogueSource)) + 0 -> navigator.push(MangaDexSimilarScreen(manga.id, source.id)) + 1 -> navigator.push(RecommendsScreen(manga.id, source.id)) } } .show() } else if (source is CatalogueSource) { - router.pushController(RecommendsController(manga, source)) + navigator.push(RecommendsScreen(manga.id, source.id)) } } // AZ <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt deleted file mode 100644 index f3c4d5bed..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt +++ /dev/null @@ -1,18 +0,0 @@ -package eu.kanade.tachiyomi.ui.more - -import androidx.compose.runtime.Composable -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.ui.base.controller.RootController - -class MoreController : BasicFullComposeController(), RootController { - - @Composable - override fun ComposeContent() { - Navigator(screen = MoreScreen) - } - - companion object { - const val URL_HELP = "https://tachiyomi.org/help/" - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt similarity index 63% rename from app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreScreen.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt index 2dab4aa9e..948a76459 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt @@ -1,32 +1,39 @@ package eu.kanade.tachiyomi.ui.more +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow +import cafe.adriel.voyager.navigator.tab.LocalTabNavigator +import cafe.adriel.voyager.navigator.tab.TabOptions import eu.kanade.core.prefs.asState import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.more.MoreScreen -import eu.kanade.presentation.util.LocalRouter +import eu.kanade.presentation.util.Tab +import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadService -import eu.kanade.tachiyomi.ui.base.controller.pushController -import eu.kanade.tachiyomi.ui.category.CategoryController -import eu.kanade.tachiyomi.ui.download.DownloadController -import eu.kanade.tachiyomi.ui.history.HistoryController -import eu.kanade.tachiyomi.ui.setting.SettingsMainController -import eu.kanade.tachiyomi.ui.stats.StatsController -import eu.kanade.tachiyomi.ui.updates.UpdatesController +import eu.kanade.tachiyomi.ui.category.CategoryScreen +import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen +import eu.kanade.tachiyomi.ui.history.HistoryTab +import eu.kanade.tachiyomi.ui.setting.SettingsScreen +import eu.kanade.tachiyomi.ui.stats.StatsScreen +import eu.kanade.tachiyomi.ui.updates.UpdatesTab import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid -import exh.ui.batchadd.BatchAddController +import exh.ui.batchadd.BatchAddScreen import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -35,11 +42,28 @@ import kotlinx.coroutines.flow.combine import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -object MoreScreen : Screen { +data class MoreTab(private val toDownloads: Boolean = false) : Tab { + + override val options: TabOptions + @Composable + get() { + val isSelected = LocalTabNavigator.current.current.key == key + val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_more_enter) + return TabOptions( + index = 4u, + title = stringResource(R.string.label_more), + icon = rememberAnimatedVectorPainter(image, isSelected), + ) + } + + override suspend fun onReselect(navigator: Navigator) { + navigator.push(SettingsScreen.toMainScreen()) + } + @Composable override fun Content() { - val router = LocalRouter.currentOrThrow val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { MoreScreenModel() } val downloadQueueState by screenModel.downloadQueueState.collectAsState() MoreScreen( @@ -53,16 +77,16 @@ object MoreScreen : Screen { showNavUpdates = screenModel.showNavUpdates, showNavHistory = screenModel.showNavHistory, // SY <-- - onClickDownloadQueue = { router.pushController(DownloadController()) }, - onClickCategories = { router.pushController(CategoryController()) }, - onClickStats = { router.pushController(StatsController()) }, - onClickBackupAndRestore = { router.pushController(SettingsMainController.toBackupScreen()) }, - onClickSettings = { router.pushController(SettingsMainController()) }, - onClickAbout = { router.pushController(SettingsMainController.toAboutScreen()) }, + onClickDownloadQueue = { navigator.push(DownloadQueueScreen) }, + onClickCategories = { navigator.push(CategoryScreen()) }, + onClickStats = { navigator.push(StatsScreen()) }, + onClickBackupAndRestore = { navigator.push(SettingsScreen.toBackupScreen()) }, + onClickSettings = { navigator.push(SettingsScreen.toMainScreen()) }, + onClickAbout = { navigator.push(SettingsScreen.toAboutScreen()) }, // SY --> - onClickBatchAdd = { router.pushController(BatchAddController()) }, - onClickUpdates = { router.pushController(UpdatesController()) }, - onClickHistory = { router.pushController(HistoryController()) }, + onClickBatchAdd = { navigator.push(BatchAddScreen()) }, + onClickUpdates = { navigator.push(UpdatesTab) }, + onClickHistory = { navigator.push(HistoryTab) }, // SY <-- ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateDialogController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateDialogController.kt deleted file mode 100644 index b7bca75a7..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateDialogController.kt +++ /dev/null @@ -1,62 +0,0 @@ -package eu.kanade.tachiyomi.ui.more - -import android.app.Dialog -import android.os.Bundle -import android.text.method.LinkMovementMethod -import android.view.View -import android.widget.TextView -import androidx.core.os.bundleOf -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.updater.AppUpdateResult -import eu.kanade.tachiyomi.data.updater.AppUpdateService -import eu.kanade.tachiyomi.ui.base.controller.DialogController -import eu.kanade.tachiyomi.ui.base.controller.openInBrowser -import io.noties.markwon.Markwon - -class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) { - - constructor(update: AppUpdateResult.NewUpdate) : this( - bundleOf( - BODY_KEY to update.release.info, - VERSION_KEY to update.release.version, - RELEASE_URL_KEY to update.release.releaseLink, - DOWNLOAD_URL_KEY to update.release.getDownloadLink(), - ), - ) - - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val releaseBody = args.getString(BODY_KEY)!! - .replace("""---(\R|.)*Checksums(\R|.)*""".toRegex(), "") - val info = Markwon.create(activity!!).toMarkdown(releaseBody) - - return MaterialAlertDialogBuilder(activity!!) - .setTitle(R.string.update_check_notification_update_available) - .setMessage(info) - .setPositiveButton(R.string.update_check_confirm) { _, _ -> - applicationContext?.let { context -> - // Start download - val url = args.getString(DOWNLOAD_URL_KEY)!! - val version = args.getString(VERSION_KEY) - AppUpdateService.start(context, url, version) - } - } - .setNeutralButton(R.string.update_check_open) { _, _ -> - openInBrowser(args.getString(RELEASE_URL_KEY)!!) - } - .create() - } - - override fun onAttach(view: View) { - super.onAttach(view) - - // Make links in Markdown text clickable - (dialog?.findViewById(android.R.id.message) as? TextView)?.movementMethod = - LinkMovementMethod.getInstance() - } -} - -private const val BODY_KEY = "NewUpdateDialogController.body" -private const val VERSION_KEY = "NewUpdateDialogController.version" -private const val RELEASE_URL_KEY = "NewUpdateDialogController.release_url" -private const val DOWNLOAD_URL_KEY = "NewUpdateDialogController.download_url" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreen.kt new file mode 100644 index 000000000..7951250ac --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreen.kt @@ -0,0 +1,41 @@ +package eu.kanade.tachiyomi.ui.more + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.more.NewUpdateScreen +import eu.kanade.tachiyomi.data.updater.AppUpdateService +import eu.kanade.tachiyomi.util.system.openInBrowser + +class NewUpdateScreen( + private val versionName: String, + private val changelogInfo: String, + private val releaseLink: String, + private val downloadLink: String, +) : Screen { + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + val context = LocalContext.current + val changelogInfoNoChecksum = remember { + changelogInfo.replace("""---(\R|.)*Checksums(\R|.)*""".toRegex(), "") + } + NewUpdateScreen( + versionName = versionName, + changelogInfo = changelogInfoNoChecksum, + onOpenInBrowser = { context.openInBrowser(releaseLink) }, + onRejectUpdate = navigator::pop, + onAcceptUpdate = { + AppUpdateService.start( + context = context, + url = downloadLink, + title = versionName, + ) + navigator.pop() + }, + ) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index dc2c93a7f..355bc1098 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -63,7 +63,6 @@ import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegateImpl import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegateImpl import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.AddToLibraryFirst import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Success @@ -85,6 +84,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer import eu.kanade.tachiyomi.ui.webview.WebViewActivity +import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.util.preference.toggle import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale import eu.kanade.tachiyomi.util.system.createReaderThemeContext @@ -458,7 +458,7 @@ class ReaderActivity : startActivity( Intent(this, MainActivity::class.java).apply { action = MainActivity.SHORTCUT_MANGA - putExtra(MangaController.MANGA_EXTRA, id) + putExtra(Constants.MANGA_EXTRA, id) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) }, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt deleted file mode 100644 index 35961763a..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt +++ /dev/null @@ -1,37 +0,0 @@ -package eu.kanade.tachiyomi.ui.setting - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -class SettingsMainController(bundle: Bundle = bundleOf()) : BasicFullComposeController(bundle) { - - private val toBackupScreen = args.getBoolean(TO_BACKUP_SCREEN) - private val toAboutScreen = args.getBoolean(TO_ABOUT_SCREEN) - - @Composable - override fun ComposeContent() { - Navigator( - screen = when { - toBackupScreen -> SettingsScreen.toBackupScreen() - toAboutScreen -> SettingsScreen.toAboutScreen() - else -> SettingsScreen.toMainScreen() - }, - ) - } - - companion object { - fun toBackupScreen(): SettingsMainController { - return SettingsMainController(bundleOf(TO_BACKUP_SCREEN to true)) - } - - fun toAboutScreen(): SettingsMainController { - return SettingsMainController(bundleOf(TO_ABOUT_SCREEN to true)) - } - } -} - -private const val TO_BACKUP_SCREEN = "to_backup_screen" -private const val TO_ABOUT_SCREEN = "to_about_screen" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt index efb4b5c94..1c6053352 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt @@ -13,7 +13,6 @@ import eu.kanade.presentation.more.settings.screen.SettingsBackupScreen import eu.kanade.presentation.more.settings.screen.SettingsGeneralScreen import eu.kanade.presentation.more.settings.screen.SettingsMainScreen import eu.kanade.presentation.util.LocalBackPress -import eu.kanade.presentation.util.LocalRouter import eu.kanade.presentation.util.Transition import eu.kanade.presentation.util.isTabletUi @@ -24,15 +23,8 @@ class SettingsScreen private constructor( @Composable override fun Content() { - val router = LocalRouter.currentOrThrow val navigator = LocalNavigator.currentOrThrow if (!isTabletUi()) { - val back: () -> Unit = { - when { - navigator.canPop -> navigator.pop() - router.backstackSize > 1 -> router.handleBack() - } - } Navigator( screen = if (toBackup) { SettingsBackupScreen @@ -42,7 +34,7 @@ class SettingsScreen private constructor( SettingsMainScreen }, content = { - CompositionLocalProvider(LocalBackPress provides back) { + CompositionLocalProvider(LocalBackPress provides navigator::pop) { ScreenTransition( navigator = it, transition = { Transition.OneWayFade }, @@ -62,7 +54,7 @@ class SettingsScreen private constructor( ) { TwoPanelBox( startContent = { - CompositionLocalProvider(LocalBackPress provides router::popCurrentController) { + CompositionLocalProvider(LocalBackPress provides navigator::pop) { SettingsMainScreen.Content(twoPane = true) } }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsController.kt deleted file mode 100644 index 115b595c5..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsController.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.tachiyomi.ui.stats - -import androidx.compose.runtime.Composable -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -class StatsController : BasicFullComposeController() { - - @Composable - override fun ComposeContent() { - Navigator(screen = StatsScreen()) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreen.kt index 5a5780aa2..b4e0f384e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreen.kt @@ -3,18 +3,17 @@ package eu.kanade.tachiyomi.ui.stats import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.uniqueScreenKey +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.more.stats.StatsScreenContent import eu.kanade.presentation.more.stats.StatsScreenState -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R class StatsScreen : Screen { @@ -23,8 +22,7 @@ class StatsScreen : Screen { @Composable override fun Content() { - val router = LocalRouter.currentOrThrow - val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { StatsScreenModel() } val state by screenModel.state.collectAsState() @@ -38,7 +36,7 @@ class StatsScreen : Screen { topBar = { scrollBehavior -> AppBar( title = stringResource(R.string.label_stats), - navigateUp = router::popCurrentController, + navigateUp = navigator::pop, scrollBehavior = scrollBehavior, ) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesController.kt deleted file mode 100644 index e26ed191c..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesController.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.tachiyomi.ui.updates - -import androidx.compose.runtime.Composable -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.ui.base.controller.RootController - -class UpdatesController : BasicFullComposeController(), RootController { - @Composable - override fun ComposeContent() { - Navigator(screen = UpdatesScreen) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt similarity index 62% rename from app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreen.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt index 304d3df66..83c4a0574 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt @@ -1,29 +1,70 @@ package eu.kanade.tachiyomi.ui.updates +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow +import cafe.adriel.voyager.navigator.tab.LocalTabNavigator +import cafe.adriel.voyager.navigator.tab.TabOptions +import eu.kanade.core.prefs.asState +import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.updates.UpdateScreen import eu.kanade.presentation.updates.UpdatesDeleteConfirmationDialog -import eu.kanade.presentation.util.LocalRouter +import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.pushController +import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen +import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel.Event import kotlinx.coroutines.flow.collectLatest +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +object UpdatesTab : Tab { + + override val options: TabOptions + @Composable + get() { + val isSelected = LocalTabNavigator.current.current.key == key + val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_updates_enter) + return TabOptions( + index = 1u, + title = stringResource(R.string.label_recent_updates), + icon = rememberAnimatedVectorPainter(image, isSelected), + ) + } + + override suspend fun onReselect(navigator: Navigator) { + navigator.push(DownloadQueueScreen) + } + + // SY --> + @Composable + override fun isEnabled(): Boolean { + val scope = rememberCoroutineScope() + return remember { + Injekt.get().showNavHistory().asState(scope) + }.value + } + // SY <-- -object UpdatesScreen : Screen { @Composable override fun Content() { val context = LocalContext.current - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { UpdatesScreenModel() } val state by screenModel.state.collectAsState() @@ -34,7 +75,7 @@ object UpdatesScreen : Screen { downloadedOnlyMode = screenModel.isDownloadOnly, lastUpdated = screenModel.lastUpdated, relativeTime = screenModel.relativeTime, - onClickCover = { item -> router.pushController(MangaController(item.update.mangaId)) }, + onClickCover = { item -> navigator.push(MangaScreen(item.update.mangaId)) }, onSelectAll = screenModel::toggleAllSelection, onInvertSelection = screenModel::invertSelection, onUpdateLibrary = screenModel::updateLibrary, @@ -77,8 +118,9 @@ object UpdatesScreen : Screen { } LaunchedEffect(state.selectionMode) { - (context as? MainActivity)?.showBottomNav(!state.selectionMode) + HomeScreen.showBottomNav(!state.selectionMode) } + LaunchedEffect(state.isLoading) { if (!state.isLoading) { (context as? MainActivity)?.ready = true diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/Constants.kt b/app/src/main/java/eu/kanade/tachiyomi/util/Constants.kt new file mode 100644 index 000000000..dadc48afb --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/Constants.kt @@ -0,0 +1,7 @@ +package eu.kanade.tachiyomi.util + +object Constants { + const val URL_HELP = "https://tachiyomi.org/help/" + + const val MANGA_EXTRA = "manga" +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt deleted file mode 100644 index 0bea9f8a0..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt +++ /dev/null @@ -1,196 +0,0 @@ -package eu.kanade.tachiyomi.widget - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.TimeInterpolator -import android.content.Context -import android.os.Parcel -import android.os.Parcelable -import android.util.AttributeSet -import android.view.ViewPropertyAnimator -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.calculateEndPadding -import androidx.compose.foundation.layout.calculateStartPadding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.max -import androidx.customview.view.AbsSavedState -import androidx.interpolator.view.animation.FastOutLinearInInterpolator -import androidx.interpolator.view.animation.LinearOutSlowInInterpolator -import com.google.android.material.bottomnavigation.BottomNavigationView -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale -import eu.kanade.tachiyomi.util.system.pxToDp -import kotlin.math.max - -class TachiyomiBottomNavigationView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = R.attr.bottomNavigationStyle, - defStyleRes: Int = R.style.Widget_Design_BottomNavigationView, -) : BottomNavigationView(context, attrs, defStyleAttr, defStyleRes) { - - private var currentAnimator: ViewPropertyAnimator? = null - - private var currentState = STATE_UP - - override fun onSaveInstanceState(): Parcelable { - val superState = super.onSaveInstanceState() - return SavedState(superState).also { - it.currentState = currentState - it.translationY = translationY - } - } - - override fun onRestoreInstanceState(state: Parcelable?) { - if (state is SavedState) { - super.onRestoreInstanceState(state.superState) - super.setTranslationY(state.translationY) - currentState = state.currentState - } else { - super.onRestoreInstanceState(state) - } - } - - override fun setTranslationY(translationY: Float) { - // Disallow translation change when state down - if (currentState == STATE_DOWN) return - super.setTranslationY(translationY) - } - - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { - super.onSizeChanged(w, h, oldw, oldh) - bottomNavPadding = h.pxToDp.dp - } - - /** - * Shows this view up. - */ - fun slideUp() = post { - currentAnimator?.cancel() - clearAnimation() - - currentState = STATE_UP - animateTranslation( - 0F, - SLIDE_UP_ANIMATION_DURATION, - LinearOutSlowInInterpolator(), - ) - bottomNavPadding = height.pxToDp.dp - } - - /** - * Hides this view down. [setTranslationY] won't work until [slideUp] is called. - */ - fun slideDown() = post { - currentAnimator?.cancel() - clearAnimation() - - currentState = STATE_DOWN - animateTranslation( - height.toFloat(), - SLIDE_DOWN_ANIMATION_DURATION, - FastOutLinearInInterpolator(), - ) - bottomNavPadding = 0.dp - } - - private fun animateTranslation(targetY: Float, duration: Long, interpolator: TimeInterpolator) { - currentAnimator = animate() - .translationY(targetY) - .setInterpolator(interpolator) - .setDuration(duration) - .applySystemAnimatorScale(context) - .setListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - postInvalidate() - } - }, - ) - } - - internal class SavedState : AbsSavedState { - var currentState = STATE_UP - var translationY = 0F - - constructor(superState: Parcelable) : super(superState) - - constructor(source: Parcel, loader: ClassLoader?) : super(source, loader) { - currentState = source.readInt() - translationY = source.readFloat() - } - - override fun writeToParcel(out: Parcel, flags: Int) { - super.writeToParcel(out, flags) - out.writeInt(currentState) - out.writeFloat(translationY) - } - - companion object { - @JvmField - val CREATOR: Parcelable.ClassLoaderCreator = object : Parcelable.ClassLoaderCreator { - override fun createFromParcel(source: Parcel, loader: ClassLoader): SavedState { - return SavedState(source, loader) - } - - override fun createFromParcel(source: Parcel): SavedState { - return SavedState(source, null) - } - - override fun newArray(size: Int): Array { - return newArray(size) - } - } - } - } - - companion object { - private const val STATE_DOWN = 1 - private const val STATE_UP = 2 - - private const val SLIDE_UP_ANIMATION_DURATION = 225L - private const val SLIDE_DOWN_ANIMATION_DURATION = 175L - - private var bottomNavPadding by mutableStateOf(0.dp) - - /** - * Merges [bottomNavPadding] to the origin's [PaddingValues] bottom side. - */ - @ReadOnlyComposable - @Composable - fun withBottomNavPadding(origin: PaddingValues = PaddingValues()): PaddingValues { - val layoutDirection = LocalLayoutDirection.current - return PaddingValues( - start = origin.calculateStartPadding(layoutDirection), - top = origin.calculateTopPadding(), - end = origin.calculateEndPadding(layoutDirection), - bottom = max(origin.calculateBottomPadding(), bottomNavPadding), - ) - } - - /** - * @see withBottomNavPadding - */ - @ReadOnlyComposable - @Composable - fun withBottomNavInset(origin: WindowInsets): WindowInsets { - val density = LocalDensity.current - val layoutDirection = LocalLayoutDirection.current - return WindowInsets( - left = origin.getLeft(density, layoutDirection), - top = origin.getTop(density), - right = origin.getRight(density, layoutDirection), - bottom = max(origin.getBottom(density), with(density) { bottomNavPadding.roundToPx() }), - ) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiChangeHandlerFrameLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiChangeHandlerFrameLayout.kt deleted file mode 100644 index 2c63ffabf..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiChangeHandlerFrameLayout.kt +++ /dev/null @@ -1,53 +0,0 @@ -package eu.kanade.tachiyomi.widget - -import android.content.Context -import android.util.AttributeSet -import androidx.coordinatorlayout.widget.CoordinatorLayout -import com.bluelinelabs.conductor.ChangeHandlerFrameLayout - -/** - * [ChangeHandlerFrameLayout] with the ability to draw behind the header sibling in [CoordinatorLayout]. - * The layout behavior of this view is set to [TachiyomiScrollingViewBehavior] and should not be changed. - */ -class TachiyomiChangeHandlerFrameLayout( - context: Context, - attrs: AttributeSet, -) : ChangeHandlerFrameLayout(context, attrs), CoordinatorLayout.AttachedBehavior { - - /** - * If true, this view will draw behind the header sibling. - * - * @see TachiyomiScrollingViewBehavior.shouldHeaderOverlap - */ - var overlapHeader = false - set(value) { - if (field != value) { - field = value - (layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = behavior.apply { - shouldHeaderOverlap = value - } - if (!value) { - // The behavior doesn't reset translationY when shouldHeaderOverlap is false - translationY = 0F - } - forceLayout() - } - } - - fun enableScrollingBehavior(enable: Boolean) { - (layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = if (enable) { - behavior.apply { - shouldHeaderOverlap = overlapHeader - } - } else { - null - } - if (!enable) { - // The behavior doesn't reset translationY when shouldHeaderOverlap is false - translationY = 0F - } - forceLayout() - } - - override fun getBehavior() = TachiyomiScrollingViewBehavior() -} diff --git a/app/src/main/java/exh/debug/SettingsDebugController.kt b/app/src/main/java/exh/debug/SettingsDebugScreen.kt similarity index 94% rename from app/src/main/java/exh/debug/SettingsDebugController.kt rename to app/src/main/java/exh/debug/SettingsDebugScreen.kt index 856332c9b..90467c9f8 100644 --- a/app/src/main/java/exh/debug/SettingsDebugController.kt +++ b/app/src/main/java/exh/debug/SettingsDebugScreen.kt @@ -1,6 +1,5 @@ package exh.debug -import android.app.Activity import android.util.Log import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade @@ -30,6 +29,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState @@ -43,6 +43,9 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.core.prefs.PreferenceMutableState import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.Divider @@ -53,8 +56,8 @@ import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer import eu.kanade.presentation.util.plus import eu.kanade.presentation.util.topSmallPaddingValues -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController import exh.util.capitalize +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -63,12 +66,17 @@ import kotlin.reflect.KFunction import kotlin.reflect.KVisibility import kotlin.reflect.full.declaredFunctions -class SettingsDebugController : BasicFullComposeController() { +class SettingsDebugScreen : Screen { data class DebugToggle(val name: String, val pref: PreferenceMutableState, val default: Boolean) @Composable - override fun ComposeContent() { + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + val scope = rememberCoroutineScope() + DisposableEffect(Unit) { + onDispose { navigator.pop() } + } val functions by produceState, String>>?>(initialValue = null) { value = withContext(Dispatchers.Default) { DebugFunctions::class.declaredFunctions.filter { @@ -82,7 +90,7 @@ class SettingsDebugController : BasicFullComposeController() { } val toggles by produceState(initialValue = emptyList()) { value = withContext(Dispatchers.Default) { - DebugToggles.values().map { DebugToggle(it.name, it.asPref(viewScope), it.default) } + DebugToggles.values().map { DebugToggle(it.name, it.asPref(scope), it.default) } } } Scaffold( @@ -96,7 +104,7 @@ class SettingsDebugController : BasicFullComposeController() { Crossfade(functions == null) { when (it) { true -> LoadingScreen() - false -> FunctionList(paddingValues, functions.orEmpty(), toggles) + false -> FunctionList(paddingValues, functions.orEmpty(), toggles, scope) } } } @@ -107,8 +115,8 @@ class SettingsDebugController : BasicFullComposeController() { paddingValues: PaddingValues, functions: List, String>>, toggles: List, + scope: CoroutineScope, ) { - val scope = rememberCoroutineScope() Box(Modifier.fillMaxSize()) { var running by remember { mutableStateOf(false) } var result by remember { mutableStateOf?>(null) } @@ -227,9 +235,4 @@ class SettingsDebugController : BasicFullComposeController() { ) } } - - override fun onActivityStopped(activity: Activity) { - super.onActivityStopped(activity) - router.popCurrentController() - } } diff --git a/app/src/main/java/exh/md/MangaDexFabHeaderAdapter.kt b/app/src/main/java/exh/md/MangaDexFabHeaderAdapter.kt index df1aa06c9..75e3e0fc7 100644 --- a/app/src/main/java/exh/md/MangaDexFabHeaderAdapter.kt +++ b/app/src/main/java/exh/md/MangaDexFabHeaderAdapter.kt @@ -4,17 +4,16 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import com.bluelinelabs.conductor.Router +import cafe.adriel.voyager.navigator.Navigator import eu.kanade.tachiyomi.databinding.SourceFilterMangadexHeaderBinding import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.online.RandomMangaSource -import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController +import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.withIOContext -import exh.md.follows.MangaDexFollowsController +import exh.md.follows.MangaDexFollowsScreen -class MangaDexFabHeaderAdapter(val router: Router, val source: CatalogueSource, val onClick: () -> Unit) : +class MangaDexFabHeaderAdapter(val navigator: Navigator, val source: CatalogueSource, val onClick: () -> Unit) : RecyclerView.Adapter() { private lateinit var binding: SourceFilterMangadexHeaderBinding @@ -33,7 +32,7 @@ class MangaDexFabHeaderAdapter(val router: Router, val source: CatalogueSource, inner class SavedSearchesViewHolder(view: View) : RecyclerView.ViewHolder(view) { fun bind() { binding.mangadexFollows.setOnClickListener { - router.replaceTopController(MangaDexFollowsController(source).withFadeTransaction()) + navigator.replace(MangaDexFollowsScreen(source.id)) onClick() } binding.mangadexRandom.setOnClickListener { @@ -41,11 +40,11 @@ class MangaDexFabHeaderAdapter(val router: Router, val source: CatalogueSource, val randomMangaUrl = withIOContext { (source as? RandomMangaSource)?.fetchRandomMangaUrl() } - router.replaceTopController( - BrowseSourceController( - source, + navigator.replace( + BrowseSourceScreen( + source.id, "id:$randomMangaUrl", - ).withFadeTransaction(), + ), ) onClick() } diff --git a/app/src/main/java/exh/md/follows/MangaDexFollowsController.kt b/app/src/main/java/exh/md/follows/MangaDexFollowsController.kt deleted file mode 100644 index c11e2d283..000000000 --- a/app/src/main/java/exh/md/follows/MangaDexFollowsController.kt +++ /dev/null @@ -1,30 +0,0 @@ -package exh.md.follows - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController - -/** - * Controller that shows the latest manga from the catalogue. Inherit [BrowseSourceController]. - */ -class MangaDexFollowsController(bundle: Bundle) : BasicFullComposeController(bundle) { - - constructor(source: CatalogueSource) : this( - bundleOf( - SOURCE_ID_KEY to source.id, - ), - ) - - private val sourceId = args.getLong(SOURCE_ID_KEY) - - @Composable - override fun ComposeContent() { - Navigator(screen = MangaDexFollowsScreen(sourceId)) - } -} - -private const val SOURCE_ID_KEY = "source_id" diff --git a/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt b/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt index 8e46e28cd..8fdf1c688 100644 --- a/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt +++ b/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt @@ -1,6 +1,5 @@ package exh.md.follows -import androidx.activity.compose.BackHandler import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable @@ -23,19 +22,16 @@ import eu.kanade.presentation.browse.components.RemoveMangaDialog import eu.kanade.presentation.components.ChangeCategoryDialog import eu.kanade.presentation.components.DuplicateMangaDialog import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.pushController import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel -import eu.kanade.tachiyomi.ui.category.CategoryController -import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.ui.category.CategoryScreen +import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.util.lang.launchIO class MangaDexFollowsScreen(private val sourceId: Long) : Screen { @Composable override fun Content() { - val router = LocalRouter.currentOrThrow val navigator = LocalNavigator.currentOrThrow val scope = rememberCoroutineScope() val haptic = LocalHapticFeedback.current @@ -44,20 +40,13 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen { val snackbarHostState = remember { SnackbarHostState() } - val navigateUp: () -> Unit = { - when { - navigator.canPop -> navigator.pop() - router.backstackSize > 1 -> router.popCurrentController() - } - } - Scaffold( topBar = { scrollBehavior -> BrowseSourceSimpleToolbar( title = stringResource(R.string.mangadex_follows), displayMode = screenModel.displayMode, onDisplayModeChange = { screenModel.displayMode = it }, - navigateUp = navigateUp, + navigateUp = navigator::pop, scrollBehavior = scrollBehavior, ) }, @@ -82,7 +71,7 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen { onWebViewClick = null, onHelpClick = null, onLocalSourceHelpClick = null, - onMangaClick = { router.pushController(MangaController(it.id, true)) }, + onMangaClick = { navigator.push(MangaScreen(it.id, true)) }, onMangaLongClick = { manga -> scope.launchIO { val duplicateManga = screenModel.getDuplicateLibraryManga(manga) @@ -109,7 +98,7 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen { DuplicateMangaDialog( onDismissRequest = onDismissRequest, onConfirm = { screenModel.addFavorite(dialog.manga) }, - onOpenManga = { router.pushController(MangaController(dialog.duplicate.id)) }, + onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, duplicateFrom = screenModel.getSourceOrStub(dialog.duplicate), ) } @@ -127,7 +116,7 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen { initialSelection = dialog.initialSelection, onDismissRequest = onDismissRequest, onEditCategories = { - router.pushController(CategoryController()) + navigator.push(CategoryScreen()) }, onConfirm = { include, _ -> screenModel.changeMangaFavorite(dialog.manga) @@ -137,7 +126,5 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen { } else -> {} } - - BackHandler(onBack = navigateUp) } } diff --git a/app/src/main/java/exh/md/follows/MangaDexFollowsScreenModel.kt b/app/src/main/java/exh/md/follows/MangaDexFollowsScreenModel.kt index ab7f206a7..14c09de44 100644 --- a/app/src/main/java/exh/md/follows/MangaDexFollowsScreenModel.kt +++ b/app/src/main/java/exh/md/follows/MangaDexFollowsScreenModel.kt @@ -1,7 +1,7 @@ package exh.md.follows import android.content.Context -import com.bluelinelabs.conductor.Router +import cafe.adriel.voyager.navigator.Navigator import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.source.model.SourcePagingSourceType import eu.kanade.tachiyomi.source.model.FilterList @@ -22,7 +22,7 @@ class MangaDexFollowsScreenModel(sourceId: Long) : BrowseSourceScreenModel(sourc return map { it to metadata } } - override fun initFilterSheet(context: Context, router: Router) { + override fun initFilterSheet(context: Context, navigator: Navigator) { // No-op: we don't allow filtering in recs } } diff --git a/app/src/main/java/exh/md/similar/MangaDexSimilarController.kt b/app/src/main/java/exh/md/similar/MangaDexSimilarController.kt deleted file mode 100644 index bb108cb51..000000000 --- a/app/src/main/java/exh/md/similar/MangaDexSimilarController.kt +++ /dev/null @@ -1,34 +0,0 @@ -package exh.md.similar - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.domain.manga.model.Manga -import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController - -/** - * Controller that shows the latest manga from the catalogue. Inherit [BrowseSourceController]. - */ -class MangaDexSimilarController(bundle: Bundle) : BasicFullComposeController(bundle) { - - constructor(manga: Manga, source: CatalogueSource) : this( - bundleOf( - MANGA_ID to manga.id, - SOURCE_ID_KEY to source.id, - ), - ) - - val mangaId = args.getLong(MANGA_ID) - val sourceId = args.getLong(SOURCE_ID_KEY) - - @Composable - override fun ComposeContent() { - Navigator(screen = MangaDexSimilarScreen(mangaId, sourceId)) - } -} - -private const val MANGA_ID = "manga_id" -private const val SOURCE_ID_KEY = "source_id" diff --git a/app/src/main/java/exh/md/similar/MangaDexSimilarScreen.kt b/app/src/main/java/exh/md/similar/MangaDexSimilarScreen.kt index 9a824b33f..339b00496 100644 --- a/app/src/main/java/exh/md/similar/MangaDexSimilarScreen.kt +++ b/app/src/main/java/exh/md/similar/MangaDexSimilarScreen.kt @@ -17,10 +17,8 @@ import eu.kanade.domain.manga.model.Manga import eu.kanade.presentation.browse.BrowseSourceContent import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.pushController -import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.ui.manga.MangaScreen class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen { @@ -28,26 +26,18 @@ class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen { override fun Content() { val screenModel = rememberScreenModel { MangaDexSimilarScreenModel(mangaId, sourceId) } val state by screenModel.state.collectAsState() - val router = LocalRouter.currentOrThrow val navigator = LocalNavigator.currentOrThrow val onMangaClick: (Manga) -> Unit = { - router.pushController(MangaController(it.id, true)) + navigator.push(MangaScreen(it.id, true)) } val snackbarHostState = remember { SnackbarHostState() } - val navigateUp: () -> Unit = { - when { - navigator.canPop -> navigator.pop() - router.backstackSize > 1 -> router.popCurrentController() - } - } - Scaffold( topBar = { scrollBehavior -> BrowseSourceSimpleToolbar( - navigateUp = navigateUp, + navigateUp = navigator::pop, title = stringResource(R.string.similar, screenModel.manga.title), displayMode = screenModel.displayMode, onDisplayModeChange = { screenModel.displayMode = it }, diff --git a/app/src/main/java/exh/md/similar/MangaDexSimilarScreenModel.kt b/app/src/main/java/exh/md/similar/MangaDexSimilarScreenModel.kt index 06d145fcd..e9a5f5073 100644 --- a/app/src/main/java/exh/md/similar/MangaDexSimilarScreenModel.kt +++ b/app/src/main/java/exh/md/similar/MangaDexSimilarScreenModel.kt @@ -1,7 +1,7 @@ package exh.md.similar import android.content.Context -import com.bluelinelabs.conductor.Router +import cafe.adriel.voyager.navigator.Navigator import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.source.model.SourcePagingSourceType @@ -16,9 +16,6 @@ import kotlinx.coroutines.runBlocking import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -/** - * Presenter of [MangaDexSimilarController]. Inherit BrowseCataloguePresenter. - */ class MangaDexSimilarScreenModel( val mangaId: Long, sourceId: Long, @@ -35,7 +32,7 @@ class MangaDexSimilarScreenModel( return map { it to metadata } } - override fun initFilterSheet(context: Context, router: Router) { + override fun initFilterSheet(context: Context, navigator: Navigator) { // No-op: we don't allow filtering in recs } } diff --git a/app/src/main/java/exh/pagepreview/PagePreviewController.kt b/app/src/main/java/exh/pagepreview/PagePreviewController.kt deleted file mode 100644 index 68a5f5ce6..000000000 --- a/app/src/main/java/exh/pagepreview/PagePreviewController.kt +++ /dev/null @@ -1,26 +0,0 @@ -package exh.pagepreview - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -class PagePreviewController : BasicFullComposeController { - - @Suppress("unused") - constructor(bundle: Bundle? = null) : super(bundle) - - constructor(mangaId: Long) : super( - bundleOf(MANGA_ID to mangaId), - ) - - @Composable - override fun ComposeContent() { - Navigator(screen = PagePreviewScreen(args.getLong(MANGA_ID, -1))) - } - - companion object { - const val MANGA_ID = "manga_id" - } -} diff --git a/app/src/main/java/exh/pagepreview/PagePreviewScreen.kt b/app/src/main/java/exh/pagepreview/PagePreviewScreen.kt index baf61ce4d..77fa99c12 100644 --- a/app/src/main/java/exh/pagepreview/PagePreviewScreen.kt +++ b/app/src/main/java/exh/pagepreview/PagePreviewScreen.kt @@ -7,8 +7,8 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalContext import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.ui.reader.ReaderActivity import exh.pagepreview.components.PagePreviewScreen @@ -19,7 +19,7 @@ class PagePreviewScreen(private val mangaId: Long) : Screen { val screenModel = rememberScreenModel { PagePreviewScreenModel(mangaId) } val context = LocalContext.current val state by screenModel.state.collectAsState() - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow PagePreviewScreen( state = state, pageDialogOpen = screenModel.pageDialogOpen, @@ -27,7 +27,7 @@ class PagePreviewScreen(private val mangaId: Long) : Screen { onOpenPage = { openPage(context, state, it) }, onOpenPageDialog = { screenModel.pageDialogOpen = true }, onDismissPageDialog = { screenModel.pageDialogOpen = false }, - navigateUp = router::popCurrentController, + navigateUp = navigator::pop, ) } diff --git a/app/src/main/java/exh/recs/RecommendsController.kt b/app/src/main/java/exh/recs/RecommendsController.kt deleted file mode 100644 index 8891b2b62..000000000 --- a/app/src/main/java/exh/recs/RecommendsController.kt +++ /dev/null @@ -1,34 +0,0 @@ -package exh.recs - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.domain.manga.model.Manga -import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController - -/** - * Controller that shows the latest manga from the catalogue. Inherit [BrowseSourceController]. - */ -class RecommendsController(bundle: Bundle) : BasicFullComposeController(bundle) { - - constructor(manga: Manga, source: CatalogueSource) : this( - bundleOf( - MANGA_ID to manga.id, - SOURCE_ID_KEY to source.id, - ), - ) - - val mangaId = args.getLong(MANGA_ID) - val sourceId = args.getLong(SOURCE_ID_KEY) - - @Composable - override fun ComposeContent() { - Navigator(screen = RecommendsScreen(mangaId, sourceId)) - } -} - -private const val MANGA_ID = "manga_id" -private const val SOURCE_ID_KEY = "source_id" diff --git a/app/src/main/java/exh/recs/RecommendsScreen.kt b/app/src/main/java/exh/recs/RecommendsScreen.kt index af13290e2..a88d705f3 100644 --- a/app/src/main/java/exh/recs/RecommendsScreen.kt +++ b/app/src/main/java/exh/recs/RecommendsScreen.kt @@ -17,7 +17,6 @@ import eu.kanade.domain.manga.model.Manga import eu.kanade.presentation.browse.BrowseSourceContent import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen { @@ -26,7 +25,6 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen { override fun Content() { val screenModel = rememberScreenModel { RecommendsScreenModel(mangaId, sourceId) } val state by screenModel.state.collectAsState() - val router = LocalRouter.currentOrThrow val navigator = LocalNavigator.currentOrThrow val onMangaClick: (Manga) -> Unit = { manga -> @@ -35,17 +33,10 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen { val snackbarHostState = remember { SnackbarHostState() } - val navigateUp: () -> Unit = { - when { - navigator.canPop -> navigator.pop() - router.backstackSize > 1 -> router.popCurrentController() - } - } - Scaffold( topBar = { scrollBehavior -> BrowseSourceSimpleToolbar( - navigateUp = navigateUp, + navigateUp = navigator::pop, title = screenModel.manga.title, displayMode = screenModel.displayMode, onDisplayModeChange = { screenModel.displayMode = it }, diff --git a/app/src/main/java/exh/recs/RecommendsScreenModel.kt b/app/src/main/java/exh/recs/RecommendsScreenModel.kt index 52e5ffcca..88743d7cc 100644 --- a/app/src/main/java/exh/recs/RecommendsScreenModel.kt +++ b/app/src/main/java/exh/recs/RecommendsScreenModel.kt @@ -8,9 +8,6 @@ import kotlinx.coroutines.runBlocking import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -/** - * Presenter of [RecommendsController]. Inherit BrowseCataloguePresenter. - */ class RecommendsScreenModel( val mangaId: Long, sourceId: Long, diff --git a/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt b/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt deleted file mode 100644 index 977318ed9..000000000 --- a/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt +++ /dev/null @@ -1,75 +0,0 @@ -package exh.uconfig - -import android.app.Dialog -import android.os.Bundle -import android.view.View -import androidx.appcompat.app.AlertDialog -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.DialogController -import eu.kanade.tachiyomi.util.lang.launchIO -import eu.kanade.tachiyomi.util.lang.launchUI -import eu.kanade.tachiyomi.util.system.toast -import exh.log.xLogE -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.cancel - -class ConfiguringDialogController : DialogController() { - private var materialDialog: AlertDialog? = null - val scope = MainScope() - - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - if (savedViewState == null) { - scope.launchIO { - try { - EHConfigurator(activity!!).configureAll() - launchUI { - activity?.toast(R.string.eh_settings_successfully_uploaded) - } - } catch (e: Exception) { - launchUI { - activity?.let { - MaterialAlertDialogBuilder(it) - .setTitle(R.string.eh_settings_configuration_failed) - .setMessage(it.getString(R.string.eh_settings_configuration_failed_message, e.message)) - .setPositiveButton(android.R.string.ok, null) - .show() - } - } - this@ConfiguringDialogController.xLogE("Configuration error!", e) - } - launchUI { - finish() - } - } - } - - return MaterialAlertDialogBuilder(activity!!) - .setTitle(R.string.eh_settings_uploading_to_server) - .setMessage(R.string.eh_settings_uploading_to_server_message) - .setCancelable(false) - .create() - .also { - materialDialog = it - } - } - - override fun onDestroyView(view: View) { - super.onDestroyView(view) - materialDialog = null - } - - override fun onDestroy() { - super.onDestroy() - scope.cancel() - } - - override fun onRestoreInstanceState(savedInstanceState: Bundle) { - super.onRestoreInstanceState(savedInstanceState) - finish() - } - - fun finish() { - router.popController(this) - } -} diff --git a/app/src/main/java/exh/uconfig/WarnConfigureDialogController.kt b/app/src/main/java/exh/uconfig/WarnConfigureDialogController.kt deleted file mode 100644 index d03de41a9..000000000 --- a/app/src/main/java/exh/uconfig/WarnConfigureDialogController.kt +++ /dev/null @@ -1,37 +0,0 @@ -package exh.uconfig - -import android.app.Dialog -import android.os.Bundle -import com.bluelinelabs.conductor.Router -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import eu.kanade.domain.UnsortedPreferences -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.DialogController -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import uy.kohesive.injekt.injectLazy - -class WarnConfigureDialogController : DialogController() { - private val prefs: UnsortedPreferences by injectLazy() - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - return MaterialAlertDialogBuilder(activity!!) - .setTitle(R.string.settings_profile_note) - .setMessage(R.string.settings_profile_note_message) - .setPositiveButton(android.R.string.ok) { _, _ -> - prefs.exhShowSettingsUploadWarning().set(false) - ConfiguringDialogController().showDialog(router) - } - .setCancelable(false) - .create() - } - - companion object { - fun uploadSettings(router: Router) { - if (Injekt.get().exhShowSettingsUploadWarning().get()) { - WarnConfigureDialogController().showDialog(router) - } else { - ConfiguringDialogController().showDialog(router) - } - } - } -} diff --git a/app/src/main/java/exh/ui/batchadd/BatchAddController.kt b/app/src/main/java/exh/ui/batchadd/BatchAddController.kt deleted file mode 100755 index 02ae194d8..000000000 --- a/app/src/main/java/exh/ui/batchadd/BatchAddController.kt +++ /dev/null @@ -1,16 +0,0 @@ -package exh.ui.batchadd - -import androidx.compose.runtime.Composable -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -/** - * Batch add screen - */ -class BatchAddController : BasicFullComposeController() { - - @Composable - override fun ComposeContent() { - Navigator(screen = BatchAddScreen()) - } -} diff --git a/app/src/main/java/exh/ui/batchadd/BatchAddScreen.kt b/app/src/main/java/exh/ui/batchadd/BatchAddScreen.kt index e5a020a08..9e190845e 100644 --- a/app/src/main/java/exh/ui/batchadd/BatchAddScreen.kt +++ b/app/src/main/java/exh/ui/batchadd/BatchAddScreen.kt @@ -34,7 +34,6 @@ import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.Button import eu.kanade.presentation.components.LazyColumn import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.util.LocalRouter import eu.kanade.presentation.util.padding import eu.kanade.presentation.util.plus import eu.kanade.tachiyomi.R @@ -46,19 +45,13 @@ class BatchAddScreen : Screen { val screenModel = rememberScreenModel { BatchAddScreenModel() } val state by screenModel.state.collectAsState() val navigator = LocalNavigator.currentOrThrow - val router = LocalRouter.currentOrThrow val context = LocalContext.current Scaffold( topBar = { scrollBehavior -> AppBar( title = stringResource(R.string.batch_add), - navigateUp = { - when { - navigator.canPop -> navigator.pop() - else -> router.popCurrentController() - } - }, + navigateUp = navigator::pop, scrollBehavior = scrollBehavior, ) }, diff --git a/app/src/main/java/exh/ui/intercept/InterceptActivity.kt b/app/src/main/java/exh/ui/intercept/InterceptActivity.kt index 1222f1c1d..21462452e 100755 --- a/app/src/main/java/exh/ui/intercept/InterceptActivity.kt +++ b/app/src/main/java/exh/ui/intercept/InterceptActivity.kt @@ -26,8 +26,8 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.util.view.setComposeContent import exh.GalleryAddEvent import exh.GalleryAdder @@ -113,7 +113,7 @@ class InterceptActivity : BaseActivity() { Intent(this, MainActivity::class.java) .setAction(MainActivity.SHORTCUT_MANGA) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - .putExtra(MangaController.MANGA_EXTRA, it.mangaId) + .putExtra(Constants.MANGA_EXTRA, it.mangaId) }, ) } diff --git a/app/src/main/java/exh/ui/metadata/MetadataViewController.kt b/app/src/main/java/exh/ui/metadata/MetadataViewController.kt deleted file mode 100644 index 5b6b6fefb..000000000 --- a/app/src/main/java/exh/ui/metadata/MetadataViewController.kt +++ /dev/null @@ -1,30 +0,0 @@ -package exh.ui.metadata - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.domain.manga.model.Manga -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController - -class MetadataViewController : BasicFullComposeController { - constructor(manga: Manga) : super( - bundleOf( - MANGA_EXTRA to manga.id, - SOURCE_EXTRA to manga.source, - ), - ) - - @Suppress("unused") - constructor(bundle: Bundle) : super(bundle) - - @Composable - override fun ComposeContent() { - Navigator(screen = MetadataViewScreen(args.getLong(MANGA_EXTRA), args.getLong(SOURCE_EXTRA))) - } - - companion object { - const val MANGA_EXTRA = "manga" - const val SOURCE_EXTRA = "source" - } -} diff --git a/app/src/main/java/exh/ui/smartsearch/SmartSearchController.kt b/app/src/main/java/exh/ui/smartsearch/SmartSearchController.kt deleted file mode 100644 index 5b7bf9317..000000000 --- a/app/src/main/java/exh/ui/smartsearch/SmartSearchController.kt +++ /dev/null @@ -1,31 +0,0 @@ -package exh.ui.smartsearch - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import cafe.adriel.voyager.navigator.Navigator -import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen -import eu.kanade.tachiyomi.util.system.getSerializableCompat - -class SmartSearchController(bundle: Bundle) : BasicFullComposeController() { - private val sourceId = bundle.getLong(ARG_SOURCE_ID, -1) - private val smartSearchConfig = bundle.getSerializableCompat(ARG_SMART_SEARCH_CONFIG)!! - - constructor(sourceId: Long, smartSearchConfig: SourcesScreen.SmartSearchConfig) : this( - bundleOf( - ARG_SOURCE_ID to sourceId, - ARG_SMART_SEARCH_CONFIG to smartSearchConfig, - ), - ) - - @Composable - override fun ComposeContent() { - Navigator(screen = SmartSearchScreen(sourceId, smartSearchConfig)) - } - - companion object { - private const val ARG_SOURCE_ID = "SOURCE_ID" - private const val ARG_SMART_SEARCH_CONFIG = "SMART_SEARCH_CONFIG" - } -} diff --git a/app/src/main/java/exh/ui/smartsearch/SmartSearchScreen.kt b/app/src/main/java/exh/ui/smartsearch/SmartSearchScreen.kt index 69cd3fc7f..7188b8c6e 100644 --- a/app/src/main/java/exh/ui/smartsearch/SmartSearchScreen.kt +++ b/app/src/main/java/exh/ui/smartsearch/SmartSearchScreen.kt @@ -19,15 +19,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController -import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen +import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.util.system.toast class SmartSearchScreen(private val sourceId: Long, private val smartSearchConfig: SourcesScreen.SmartSearchConfig) : Screen { @@ -36,27 +35,27 @@ class SmartSearchScreen(private val sourceId: Long, private val smartSearchConfi override fun Content() { val screenModel = rememberScreenModel { SmartSearchScreenModel(sourceId, smartSearchConfig) } - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val context = LocalContext.current val state by screenModel.state.collectAsState() LaunchedEffect(state) { val results = state if (results != null) { if (results is SmartSearchScreenModel.SearchResults.Found) { - val transaction = MangaController(results.manga.id, true, smartSearchConfig).withFadeTransaction() - router.replaceTopController(transaction) + navigator.replace(MangaScreen(results.manga.id, true, smartSearchConfig)) } else { if (results is SmartSearchScreenModel.SearchResults.NotFound) { context.toast(R.string.could_not_find_entry) } else { context.toast(R.string.automatic_search_error) } - val transaction = BrowseSourceController( - screenModel.source, - smartSearchConfig.origTitle, - smartSearchConfig, - ).withFadeTransaction() - router.replaceTopController(transaction) + navigator.push( + BrowseSourceScreen( + sourceId = screenModel.source.id, + query = smartSearchConfig.origTitle, + smartSearchConfig = smartSearchConfig, + ), + ) } } } @@ -65,7 +64,7 @@ class SmartSearchScreen(private val sourceId: Long, private val smartSearchConfi topBar = { scrollBehavior -> AppBar( title = screenModel.source.name, - navigateUp = router::popCurrentController, + navigateUp = navigator::pop, scrollBehavior = scrollBehavior, ) }, diff --git a/app/src/main/res/drawable/anim_more_enter.xml b/app/src/main/res/drawable/anim_more_enter.xml index 08f22d3b3..37e1b67fc 100644 --- a/app/src/main/res/drawable/anim_more_enter.xml +++ b/app/src/main/res/drawable/anim_more_enter.xml @@ -2,8 +2,8 @@ xmlns:aapt="http://schemas.android.com/aapt"> diff --git a/app/src/main/res/layout-sw720dp/main_activity.xml b/app/src/main/res/layout-sw720dp/main_activity.xml deleted file mode 100644 index 8d5efb178..000000000 --- a/app/src/main/res/layout-sw720dp/main_activity.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/changelog_header_layout.xml b/app/src/main/res/layout/changelog_header_layout.xml deleted file mode 100755 index f26d73102..000000000 --- a/app/src/main/res/layout/changelog_header_layout.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout/changelog_row_layout.xml b/app/src/main/res/layout/changelog_row_layout.xml deleted file mode 100755 index 4052c2706..000000000 --- a/app/src/main/res/layout/changelog_row_layout.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml deleted file mode 100755 index 5e08fb8ef..000000000 --- a/app/src/main/res/layout/main_activity.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/raw/changelog_release.xml b/app/src/main/res/raw/changelog_release.xml index ab037a5be..bfa856f7e 100755 --- a/app/src/main/res/raw/changelog_release.xml +++ b/app/src/main/res/raw/changelog_release.xml @@ -115,7 +115,7 @@ EHentai updater should now die less Fix 8Muses delegation Logs now output to file, logging to file is now useful, and can be used to debug - In the browse/latest menu errors now pupulate instead of just saying no results found + In the browse/latest menu errors now populate instead of just saying no results found Fix download issue with scanlator for merged manga Fix Migration getting stuck Fix crash when opening chapter from manual search in migration diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a3cd49343..2e67ae215 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,8 @@ coil_version = "2.2.2" shizuku_version = "12.2.0" sqldelight = "1.5.4" leakcanary = "2.10" -voyager = "1.0.0-rc06" +voyager = "1.0.0-rc07" +richtext = "0.15.0" [libraries] desugar = "com.android.tools:desugar_jdk_libs:1.2.2" @@ -52,7 +53,8 @@ image-decoder = "com.github.tachiyomiorg:image-decoder:7879b45" natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1" -markwon = "io.noties.markwon:core:4.6.2" +richtext-commonmark = { module = "com.halilibo.compose-richtext:richtext-commonmark", version.ref = "richtext" } +richtext-m3 = { module = "com.halilibo.compose-richtext:richtext-ui-material3", version.ref = "richtext" } material = "com.google.android.material:material:1.7.0" flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533" @@ -63,8 +65,6 @@ insetter = "dev.chrisbanes.insetter:insetter:0.6.1" cascade = "me.saket.cascade:cascade-compose:2.0.0-beta1" wheelpicker = "com.github.commandiron:WheelPickerCompose:1.0.11" -conductor = "com.bluelinelabs:conductor:3.1.8" - flowbinding-android = "io.github.reactivecircus.flowbinding:flowbinding-android:1.2.0" logcat = "com.squareup.logcat:logcat:0.1" @@ -89,6 +89,7 @@ sqldelight-gradle = { module = "com.squareup.sqldelight:gradle-plugin", version. junit = "org.junit.jupiter:junit-jupiter:5.9.1" voyager-navigator = { module = "ca.gosyer:voyager-navigator", version.ref = "voyager" } +voyager-tab-navigator = { module = "ca.gosyer:voyager-tab-navigator", version.ref = "voyager" } voyager-transitions = { module = "ca.gosyer:voyager-transitions", version.ref = "voyager" } [bundles] @@ -99,7 +100,8 @@ sqlite = ["sqlitektx", "sqlite-android"] nucleus = ["nucleus-core", "nucleus-supportv7"] coil = ["coil-core", "coil-gif", "coil-compose"] shizuku = ["shizuku-api", "shizuku-provider"] -voyager = ["voyager-navigator", "voyager-transitions"] +voyager = ["voyager-navigator", "voyager-tab-navigator", "voyager-transitions"] +richtext = ["richtext-commonmark", "richtext-m3"] [plugins] kotlinter = { id = "org.jmailen.kotlinter", version = "3.12.0" } diff --git a/gradle/sy.versions.toml b/gradle/sy.versions.toml index 5ee46b6a7..d4d281113 100644 --- a/gradle/sy.versions.toml +++ b/gradle/sy.versions.toml @@ -6,7 +6,6 @@ firebase-analytics = "com.google.firebase:firebase-analytics-ktx:21.0.0" firebase-crashlytics-ktx = "com.google.firebase:firebase-crashlytics-ktx:18.2.11" firebase-crashlytics-gradle = "com.google.firebase:firebase-crashlytics-gradle:2.8.0" -changelog = "com.github.gabrielemariotti.changeloglib:changelog:2.1.0" simularity = "info.debatty:java-string-similarity:2.0.0" xlog = "com.elvishew:xlog:1.11.0" diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 3508935a8..42ff04892 100755 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -145,6 +145,7 @@ Refresh Start downloading now FAQ and Guides + Not now Loading… diff --git a/i18n/src/main/res/values/strings_sy.xml b/i18n/src/main/res/values/strings_sy.xml index 5d4fa1f1e..f3ede8f06 100644 --- a/i18n/src/main/res/values/strings_sy.xml +++ b/i18n/src/main/res/values/strings_sy.xml @@ -38,6 +38,9 @@ E/ExHentai login, gallery sync MangaDex login, follows sync + + Version %1$s + E-Hentai Website Account Settings Enable ExHentai