From e64e50bf38da00efaaf3d7892958732681d13b48 Mon Sep 17 00:00:00 2001 From: HapeLee <63206378+HapeLee@users.noreply.github.com> Date: Fri, 15 May 2026 21:20:49 +0800 Subject: [PATCH] =?UTF-8?q?=E7=95=8C=E9=9D=A2=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 10 - .../java/io/legado/app/constant/PreferKey.kt | 1 + .../java/io/legado/app/data/dao/BookDao.kt | 52 +- .../main/java/io/legado/app/di/appModule.kt | 20 +- .../config/bookshelfConfig/BookshelfConfig.kt | 11 +- .../io/legado/app/ui/main/MainActivity.kt | 953 ++---------------- .../java/io/legado/app/ui/main/MainIntent.kt | 152 +++ .../io/legado/app/ui/main/MainNavGraph.kt | 423 ++++++++ .../java/io/legado/app/ui/main/MainNavKey.kt | 106 ++ .../io/legado/app/ui/main/MainNavigator.kt | 253 +++++ .../java/io/legado/app/ui/main/MainScreen.kt | 6 +- .../legado/app/ui/main/bookshelf/BookItem.kt | 177 ++-- .../ui/main/bookshelf/BookshelfConfigSheet.kt | 288 +++--- .../app/ui/main/bookshelf/BookshelfScreen.kt | 4 +- .../io/legado/app/ui/main/rss/RssScreen.kt | 12 +- .../ui/rss/favorites/RssFavoritesActivity.kt | 52 - .../ui/rss/favorites/RssFavoritesScreen.kt | 4 +- .../ui/rss/subscription/RuleSubActivity.kt | 23 - .../app/ui/rss/subscription/RuleSubScreen.kt | 6 +- .../io/legado/app/ui/theme/AdaptivePadding.kt | 4 +- .../widget/components/book/SearchBookItem.kt | 129 +-- .../widget/components/divider/PillDivider.kt | 16 +- .../settingItem/CompactSettingItems.kt | 8 +- 23 files changed, 1436 insertions(+), 1274 deletions(-) create mode 100644 app/src/main/java/io/legado/app/ui/main/MainIntent.kt create mode 100644 app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt create mode 100644 app/src/main/java/io/legado/app/ui/main/MainNavKey.kt create mode 100644 app/src/main/java/io/legado/app/ui/main/MainNavigator.kt delete mode 100644 app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt delete mode 100644 app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubActivity.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fb2dfc5c7..6fe50aa6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -193,11 +193,6 @@ android:name=".ui.qrcode.QrCodeActivity" android:enableOnBackInvokedCallback="true" android:launchMode="singleTask" /> - - - - > @@ -741,8 +741,8 @@ interface BookDao { WHERE type & ${BookType.text} > 0 AND type & ${BookType.local} = 0 AND ((SELECT COALESCE(SUM(groupId), 0) FROM book_groups WHERE groupId > 0) & `group`) = 0 AND (SELECT show FROM book_groups WHERE groupId = ${BookGroup.IdNetNone}) != 1 - ORDER BY CASE WHEN (coverUrl IS NOT NULL OR customCoverUrl IS NOT NULL) THEN 0 ELSE 1 END, durChapterTime DESC - LIMIT 4 + ORDER BY durChapterTime DESC + LIMIT 10 """ ) fun flowBookShelfRootPreview(): Flow> @@ -757,8 +757,8 @@ interface BookDao { ifnull(customIntro, intro) as intro, kind FROM books WHERE type & ${BookType.local} > 0 - ORDER BY CASE WHEN (coverUrl IS NOT NULL OR customCoverUrl IS NOT NULL) THEN 0 ELSE 1 END, durChapterTime DESC - LIMIT 4 + ORDER BY durChapterTime DESC + LIMIT 10 """ ) fun flowBookShelfLocalPreview(): Flow> @@ -773,8 +773,8 @@ interface BookDao { ifnull(customIntro, intro) as intro, kind FROM books WHERE type & ${BookType.audio} > 0 - ORDER BY CASE WHEN (coverUrl IS NOT NULL OR customCoverUrl IS NOT NULL) THEN 0 ELSE 1 END, durChapterTime DESC - LIMIT 4 + ORDER BY durChapterTime DESC + LIMIT 10 """ ) fun flowBookShelfAudioPreview(): Flow> @@ -790,8 +790,8 @@ interface BookDao { FROM books WHERE type & ${BookType.audio} = 0 AND type & ${BookType.local} = 0 AND ((SELECT COALESCE(SUM(groupId), 0) FROM book_groups WHERE groupId > 0) & `group`) = 0 - ORDER BY CASE WHEN (coverUrl IS NOT NULL OR customCoverUrl IS NOT NULL) THEN 0 ELSE 1 END, durChapterTime DESC - LIMIT 4 + ORDER BY durChapterTime DESC + LIMIT 10 """ ) fun flowBookShelfNetNoGroupPreview(): Flow> @@ -807,8 +807,8 @@ interface BookDao { FROM books WHERE type & ${BookType.local} > 0 AND ((SELECT COALESCE(SUM(groupId), 0) FROM book_groups WHERE groupId > 0) & `group`) = 0 - ORDER BY CASE WHEN (coverUrl IS NOT NULL OR customCoverUrl IS NOT NULL) THEN 0 ELSE 1 END, durChapterTime DESC - LIMIT 4 + ORDER BY durChapterTime DESC + LIMIT 10 """ ) fun flowBookShelfLocalNoGroupPreview(): Flow> @@ -823,8 +823,8 @@ interface BookDao { ifnull(customIntro, intro) as intro, kind FROM books WHERE type & ${BookType.image} > 0 - ORDER BY CASE WHEN (coverUrl IS NOT NULL OR customCoverUrl IS NOT NULL) THEN 0 ELSE 1 END, durChapterTime DESC - LIMIT 4 + ORDER BY durChapterTime DESC + LIMIT 10 """ ) fun flowBookShelfMangaPreview(): Flow> @@ -839,8 +839,8 @@ interface BookDao { ifnull(customIntro, intro) as intro, kind FROM books WHERE type & ${BookType.text} > 0 - ORDER BY CASE WHEN (coverUrl IS NOT NULL OR customCoverUrl IS NOT NULL) THEN 0 ELSE 1 END, durChapterTime DESC - LIMIT 4 + ORDER BY durChapterTime DESC + LIMIT 10 """ ) fun flowBookShelfTextPreview(): Flow> @@ -855,8 +855,8 @@ interface BookDao { ifnull(customIntro, intro) as intro, kind FROM books WHERE type & ${BookType.updateError} > 0 - ORDER BY CASE WHEN (coverUrl IS NOT NULL OR customCoverUrl IS NOT NULL) THEN 0 ELSE 1 END, durChapterTime DESC - LIMIT 4 + ORDER BY durChapterTime DESC + LIMIT 10 """ ) fun flowBookShelfUpdateErrorPreview(): Flow> @@ -871,8 +871,8 @@ interface BookDao { ifnull(customIntro, intro) as intro, kind FROM books WHERE durChapterIndex = 0 AND durChapterPos = 0 - ORDER BY CASE WHEN (coverUrl IS NOT NULL OR customCoverUrl IS NOT NULL) THEN 0 ELSE 1 END, durChapterTime DESC - LIMIT 4 + ORDER BY durChapterTime DESC + LIMIT 10 """ ) fun flowBookShelfUnreadPreview(): Flow> @@ -887,8 +887,8 @@ interface BookDao { ifnull(customIntro, intro) as intro, kind FROM books WHERE totalChapterNum > 0 AND durChapterIndex > 0 AND durChapterIndex < totalChapterNum - 1 - ORDER BY CASE WHEN (coverUrl IS NOT NULL OR customCoverUrl IS NOT NULL) THEN 0 ELSE 1 END, durChapterTime DESC - LIMIT 4 + ORDER BY durChapterTime DESC + LIMIT 10 """ ) fun flowBookShelfReadingPreview(): Flow> @@ -903,8 +903,8 @@ interface BookDao { ifnull(customIntro, intro) as intro, kind FROM books WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1 - ORDER BY CASE WHEN (coverUrl IS NOT NULL OR customCoverUrl IS NOT NULL) THEN 0 ELSE 1 END, durChapterTime DESC - LIMIT 4 + ORDER BY durChapterTime DESC + LIMIT 10 """ ) fun flowBookShelfReadFinishedPreview(): Flow> @@ -919,8 +919,8 @@ interface BookDao { ifnull(customIntro, intro) as intro, kind FROM books WHERE (`group` & :groupId) > 0 - ORDER BY CASE WHEN (coverUrl IS NOT NULL OR customCoverUrl IS NOT NULL) THEN 0 ELSE 1 END, durChapterTime DESC - LIMIT 4 + ORDER BY durChapterTime DESC + LIMIT 10 """ ) fun flowBookShelfPreviewByUserGroup(groupId: Long): Flow> diff --git a/app/src/main/java/io/legado/app/di/appModule.kt b/app/src/main/java/io/legado/app/di/appModule.kt index 7dba6810c..b9834e025 100644 --- a/app/src/main/java/io/legado/app/di/appModule.kt +++ b/app/src/main/java/io/legado/app/di/appModule.kt @@ -14,23 +14,23 @@ import io.legado.app.data.repository.BookRepository import io.legado.app.data.repository.BookSourceCallbackRepository import io.legado.app.data.repository.CacheBookDownloadRepository import io.legado.app.data.repository.DatabaseMaintenanceRepository -import io.legado.app.data.repository.DirectLinkUploadRepository import io.legado.app.data.repository.DictRuleRepository +import io.legado.app.data.repository.DirectLinkUploadRepository import io.legado.app.data.repository.ExploreRepository import io.legado.app.data.repository.ExploreRepositoryImpl import io.legado.app.data.repository.LocalBookRepository import io.legado.app.data.repository.ReadRecordRepository import io.legado.app.data.repository.RemoteBookRepository import io.legado.app.data.repository.RssRepository +import io.legado.app.data.repository.SearchContentRepository import io.legado.app.data.repository.SearchRepository import io.legado.app.data.repository.SearchRepositoryImpl -import io.legado.app.data.repository.SearchContentRepository import io.legado.app.data.repository.SettingsRepository import io.legado.app.data.repository.UploadRepository import io.legado.app.data.repository.WebDavBackupRepository import io.legado.app.data.repository.WebDavReadingProgressRepository -import io.legado.app.domain.gateway.BookCacheCleanupGateway import io.legado.app.domain.gateway.AppStartupGateway +import io.legado.app.domain.gateway.BookCacheCleanupGateway import io.legado.app.domain.gateway.BookCacheDownloadGateway import io.legado.app.domain.gateway.BookSearchGateway import io.legado.app.domain.gateway.BookSourceCallbackGateway @@ -47,14 +47,13 @@ import io.legado.app.domain.usecase.ClearBookCacheUseCase import io.legado.app.domain.usecase.DeleteBooksUseCase import io.legado.app.domain.usecase.GetReadingProgressUseCase import io.legado.app.domain.usecase.RemoveBookGroupAssignmentUseCase -import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase import io.legado.app.domain.usecase.ResolveBookShelfStateUseCase -import io.legado.app.domain.usecase.readRecord.GetReadRecordOverviewUseCase import io.legado.app.domain.usecase.SearchBooksUseCase import io.legado.app.domain.usecase.ShrinkDatabaseUseCase import io.legado.app.domain.usecase.UpdateBooksGroupUseCase import io.legado.app.domain.usecase.UploadReadingProgressUseCase import io.legado.app.domain.usecase.WebDavBackupUseCase +import io.legado.app.domain.usecase.readRecord.GetReadRecordOverviewUseCase import io.legado.app.help.coil.CoverFetcher import io.legado.app.help.coil.CoverInterceptor import io.legado.app.help.http.okHttpClient @@ -64,7 +63,6 @@ import io.legado.app.ui.book.cache.manage.BookCacheManageViewModel import io.legado.app.ui.book.changecover.ChangeCoverViewModel import io.legado.app.ui.book.changesource.ChangeBookSourceComposeViewModel import io.legado.app.ui.book.changesource.ChangeBookSourceViewModel -import io.legado.app.ui.book.manage.BookshelfManageScreenViewModel import io.legado.app.ui.book.explore.ExploreShowViewModel import io.legado.app.ui.book.group.GroupViewModel import io.legado.app.ui.book.import.local.ImportBookViewModel @@ -72,10 +70,11 @@ import io.legado.app.ui.book.import.remote.RemoteBookViewModel import io.legado.app.ui.book.import.remote.ServerConfigViewModel import io.legado.app.ui.book.import.remote.ServersViewModel import io.legado.app.ui.book.info.BookInfoViewModel +import io.legado.app.ui.book.manage.BookshelfManageScreenViewModel import io.legado.app.ui.book.manga.ReadMangaViewModel import io.legado.app.ui.book.read.ReadBookViewModel -import io.legado.app.ui.book.readRecord.ReadRecordViewModel import io.legado.app.ui.book.readRecord.ReadRecordOverviewViewModel +import io.legado.app.ui.book.readRecord.ReadRecordViewModel import io.legado.app.ui.book.search.SearchViewModel import io.legado.app.ui.book.searchContent.SearchContentViewModel import io.legado.app.ui.book.toc.TocViewModel @@ -97,10 +96,13 @@ import io.legado.app.ui.main.rss.RssViewModel import io.legado.app.ui.replace.ReplaceEditRoute import io.legado.app.ui.replace.ReplaceRuleViewModel import io.legado.app.ui.replace.edit.ReplaceEditViewModel -import io.legado.app.ui.rss.source.manage.RssSourceViewModel import io.legado.app.ui.rss.article.RssArticlesViewModel import io.legado.app.ui.rss.article.RssSortViewModel +import io.legado.app.ui.rss.favorites.RssFavoritesViewModel import io.legado.app.ui.rss.read.ReadRssViewModel +import io.legado.app.ui.rss.source.manage.RssSourceViewModel +import io.legado.app.ui.rss.subscription.RuleSubViewModel +import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase import org.koin.core.module.dsl.singleOf import org.koin.core.module.dsl.viewModel import org.koin.core.module.dsl.viewModelOf @@ -179,6 +181,8 @@ val appModule = module { viewModelOf(::RssSortViewModel) viewModelOf(::RssArticlesViewModel) viewModelOf(::ReadRssViewModel) + viewModelOf(::RssFavoritesViewModel) + viewModelOf(::RuleSubViewModel) viewModelOf(::ReadRecordViewModel) viewModelOf(::ReadRecordOverviewViewModel) viewModelOf(::ExploreShowViewModel) diff --git a/app/src/main/java/io/legado/app/ui/config/bookshelfConfig/BookshelfConfig.kt b/app/src/main/java/io/legado/app/ui/config/bookshelfConfig/BookshelfConfig.kt index 0faf4ac07..760baa44f 100644 --- a/app/src/main/java/io/legado/app/ui/config/bookshelfConfig/BookshelfConfig.kt +++ b/app/src/main/java/io/legado/app/ui/config/bookshelfConfig/BookshelfConfig.kt @@ -269,12 +269,17 @@ object BookshelfConfig { /** * 书架卡片背景颜色 */ - private val _bookshelfCardColor = prefStateDelegate(PreferKey.bookshelfCardColor, 0) { - postEvent(EventBus.NOTIFY_MAIN, false) - } + private val _bookshelfCardColor = prefStateDelegate(PreferKey.bookshelfCardColor, 0) var bookshelfCardColor by _bookshelfCardColor val bookshelfCardColorState: State get() = _bookshelfCardColor.state + /** + * 书架卡片背景颜色 (夜间) + */ + private val _bookshelfCardColorDark = prefStateDelegate(PreferKey.bookshelfCardColorDark, 0) + var bookshelfCardColorDark by _bookshelfCardColorDark + val bookshelfCardColorDarkState: State get() = _bookshelfCardColorDark.state + /** * 文件夹在列表模式下的样式: 0: 默认, 2: 横排封面 */ diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index b20d34050..3c7bee3b6 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -15,18 +15,12 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleOut import androidx.compose.animation.togetherWith -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.navigation3.runtime.NavKey -import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberNavBackStack -import androidx.navigation3.ui.LocalNavAnimatedContentScope import androidx.navigation3.ui.NavDisplay import io.legado.app.BuildConfig import io.legado.app.R @@ -43,49 +37,18 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.service.WebService import io.legado.app.ui.about.CrashLogsDialog import io.legado.app.ui.about.UpdateDialog -import io.legado.app.ui.book.cache.manage.BookCacheManageRouteScreen -import io.legado.app.ui.book.explore.ExploreShowScreen -import io.legado.app.ui.book.info.BookInfoRouteScreen -import io.legado.app.ui.book.info.BookInfoViewModel -import io.legado.app.ui.book.import.local.ImportBookScreen -import io.legado.app.ui.book.import.remote.RemoteBookScreen -import io.legado.app.ui.book.search.SearchIntent -import io.legado.app.ui.book.search.SearchScreen -import io.legado.app.ui.book.search.SearchViewModel -import io.legado.app.ui.book.readRecord.ReadRecordOverviewScreen -import io.legado.app.ui.book.readRecord.ReadRecordScreen -import io.legado.app.ui.book.readRecord.ReadRecordViewModel -import io.legado.app.ui.book.source.manage.BookSourceActivity -import io.legado.app.ui.book.manage.BookshelfManageRouteScreen import io.legado.app.ui.book.read.ReadBookActivity -import io.legado.app.ui.config.ConfigNavScreen -import io.legado.app.ui.config.ConfigTag -import io.legado.app.ui.config.backupConfig.BackupConfigScreen -import io.legado.app.ui.config.coverConfig.CoverConfigScreen -import io.legado.app.ui.config.downloadCacheConfig.DownloadCacheConfigScreen import io.legado.app.ui.config.themeConfig.ThemeConfig -import io.legado.app.ui.config.otherConfig.OtherConfigScreen -import io.legado.app.ui.config.customTheme.CustomThemeScreen -import io.legado.app.ui.config.readConfig.ReadConfigScreen -import io.legado.app.ui.config.themeConfig.ThemeConfigScreen -import io.legado.app.ui.config.themeManage.ThemeManageScreen -import io.legado.app.ui.rss.article.MainRouteRssSort -import io.legado.app.ui.rss.article.RssSortRouteScreen -import io.legado.app.ui.rss.read.MainRouteRssRead -import io.legado.app.ui.rss.read.RssReadRouteScreen import io.legado.app.ui.welcome.WelcomeActivity import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.VariableDialog import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.showDialogFragment import io.legado.app.utils.startActivity -import io.legado.app.utils.startActivityForBook import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.serialization.Serializable -import org.koin.androidx.compose.koinViewModel import org.koin.androidx.viewmodel.ext.android.viewModel import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -96,81 +59,19 @@ import kotlin.coroutines.suspendCoroutine open class MainActivity : BaseComposeActivity(), VariableDialog.Callback { companion object { - const val EXTRA_START_ROUTE = "startRoute" - private const val ROUTE_MAIN = "main" - private const val ROUTE_SETTINGS = "settings" - private const val ROUTE_SETTINGS_OTHER = "settings/other" - private const val ROUTE_SETTINGS_READ = "settings/read" - private const val ROUTE_SETTINGS_COVER = "settings/cover" - private const val ROUTE_SETTINGS_THEME = "settings/theme" - private const val ROUTE_SETTINGS_BACKUP = "settings/backup" - private const val ROUTE_SETTINGS_CUSTOM_THEME = "settings/custom_theme" - private const val ROUTE_SETTINGS_DOWNLOAD_CACHE = "settings/download_cache" - private const val ROUTE_IMPORT_LOCAL = "import/local" - private const val ROUTE_IMPORT_REMOTE = "import/remote" - private const val ROUTE_CACHE = "cache" - private const val ROUTE_BOOK_CACHE_MANAGE = "book/cache/manage" - private const val ROUTE_SEARCH = "search" - private const val ROUTE_BOOK_INFO = "book/info" - private const val ROUTE_EXPLORE_SHOW = "explore/show" - private const val ROUTE_RSS_SORT = "rss/sort" - private const val ROUTE_RSS_READ = "rss/read" - private const val ROUTE_READ_RECORD = "read_record" - private const val ROUTE_READ_RECORD_OVERVIEW = "read_record_overview" - private const val EXTRA_CACHE_GROUP_ID = "extra_cache_group_id" - private const val EXTRA_SEARCH_KEY = "extra_search_key" - private const val EXTRA_SEARCH_SCOPE = "extra_search_scope" - private const val EXTRA_BOOK_NAME = "name" - private const val EXTRA_BOOK_AUTHOR = "author" - private const val EXTRA_BOOK_URL = "bookUrl" - private const val EXTRA_EXPLORE_NAME = "exploreName" - private const val EXTRA_SOURCE_URL = "sourceUrl" - private const val EXTRA_EXPLORE_URL = "exploreUrl" + fun createLauncherIntent(context: Context): Intent = + MainIntent.createLauncherIntent(context) - private const val EXTRA_RSS_SOURCE_URL = "extra_rss_source_url" - private const val EXTRA_RSS_SORT_URL = "extra_rss_sort_url" - private const val EXTRA_RSS_KEY = "extra_rss_key" - - private const val EXTRA_RSS_READ_TITLE = "extra_rss_read_title" - private const val EXTRA_RSS_READ_ORIGIN = "extra_rss_read_origin" - private const val EXTRA_RSS_READ_LINK = "extra_rss_read_link" - private const val EXTRA_RSS_READ_OPEN_URL = "extra_rss_read_open_url" - - fun createLauncherIntent(context: Context): Intent { - val launcherComponent = - context.packageManager.getLaunchIntentForPackage(context.packageName)?.component - return if (launcherComponent != null) { - Intent().setComponent(launcherComponent) - } else { - Intent(context, MainActivity::class.java) - } - } - - fun createHomeIntent(context: Context): Intent { - return createLauncherIntent(context).apply { - putExtra(EXTRA_START_ROUTE, ROUTE_MAIN) - } - } - - fun createIntent(context: Context, configTag: String? = null): Intent { - return createLauncherIntent(context).apply { - putExtra(EXTRA_START_ROUTE, routeForConfigTag(configTag)) - } - } + fun createHomeIntent(context: Context): Intent = MainIntent.createHomeIntent(context) + fun createIntent(context: Context, configTag: String? = null): Intent = + MainIntent.createIntent(context, configTag) fun createRssSortIntent( context: Context, sourceUrl: String, sortUrl: String? = null, key: String? = null - ): Intent { - return createLauncherIntent(context).apply { - putExtra(EXTRA_START_ROUTE, ROUTE_RSS_SORT) - putExtra(EXTRA_RSS_SOURCE_URL, sourceUrl) - putExtra(EXTRA_RSS_SORT_URL, sortUrl) - putExtra(EXTRA_RSS_KEY, key) - } - } + ): Intent = MainIntent.createRssSortIntent(context, sourceUrl, sortUrl, key) fun createRssReadIntent( context: Context, @@ -178,165 +79,42 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback { origin: String, link: String? = null, openUrl: String? = null - ): Intent { - return createLauncherIntent(context).apply { - putExtra(EXTRA_START_ROUTE, ROUTE_RSS_READ) - putExtra(EXTRA_RSS_READ_TITLE, title) - putExtra(EXTRA_RSS_READ_ORIGIN, origin) - putExtra(EXTRA_RSS_READ_LINK, link) - putExtra(EXTRA_RSS_READ_OPEN_URL, openUrl) - } - } + ): Intent = MainIntent.createRssReadIntent(context, title, origin, link, openUrl) - fun createBookshelfManageScreenIntent( - context: Context, - groupId: Long = -1L - ): Intent { - return createLauncherIntent(context).apply { - putExtra(EXTRA_START_ROUTE, ROUTE_CACHE) - putExtra(EXTRA_CACHE_GROUP_ID, groupId) - } - } + fun createBookshelfManageScreenIntent(context: Context, groupId: Long = -1L): Intent = + MainIntent.createBookshelfManageScreenIntent(context, groupId) - fun createCacheIntent( - context: Context, - groupId: Long = -1L - ): Intent = createBookshelfManageScreenIntent(context, groupId) + fun createCacheIntent(context: Context, groupId: Long = -1L): Intent = + MainIntent.createCacheIntent(context, groupId) - fun createBookCacheManageIntent(context: Context): Intent { - return createLauncherIntent(context).apply { - putExtra(EXTRA_START_ROUTE, ROUTE_BOOK_CACHE_MANAGE) - } - } + fun createBookCacheManageIntent(context: Context): Intent = + MainIntent.createBookCacheManageIntent(context) fun createSearchIntent( context: Context, key: String? = null, scopeRaw: String? = null - ): Intent { - return createLauncherIntent(context).apply { - putExtra(EXTRA_START_ROUTE, ROUTE_SEARCH) - putExtra(EXTRA_SEARCH_KEY, key) - putExtra(EXTRA_SEARCH_SCOPE, scopeRaw) - } - } + ): Intent = MainIntent.createSearchIntent(context, key, scopeRaw) fun createBookInfoIntent( context: Context, name: String? = null, author: String? = null, bookUrl: String - ): Intent { - return createLauncherIntent(context).apply { - putExtra(EXTRA_START_ROUTE, ROUTE_BOOK_INFO) - putExtra(EXTRA_BOOK_NAME, name) - putExtra(EXTRA_BOOK_AUTHOR, author) - putExtra(EXTRA_BOOK_URL, bookUrl) - } - } + ): Intent = MainIntent.createBookInfoIntent(context, name, author, bookUrl) fun createExploreShowIntent( context: Context, exploreName: String? = null, sourceUrl: String, exploreUrl: String? = null - ): Intent { - return createLauncherIntent(context).apply { - putExtra(EXTRA_START_ROUTE, ROUTE_EXPLORE_SHOW) - putExtra(EXTRA_EXPLORE_NAME, exploreName) - putExtra(EXTRA_SOURCE_URL, sourceUrl) - putExtra(EXTRA_EXPLORE_URL, exploreUrl) - } - } - - private fun routeForConfigTag(configTag: String?): String { - return when (configTag) { - ConfigTag.OTHER_CONFIG -> ROUTE_SETTINGS_OTHER - ConfigTag.READ_CONFIG -> ROUTE_SETTINGS_READ - ConfigTag.COVER_CONFIG -> ROUTE_SETTINGS_COVER - ConfigTag.THEME_CONFIG -> ROUTE_SETTINGS_THEME - ConfigTag.BACKUP_CONFIG -> ROUTE_SETTINGS_BACKUP - ConfigTag.DOWNLOAD_CACHE_CONFIG -> ROUTE_SETTINGS_DOWNLOAD_CACHE - else -> ROUTE_SETTINGS - } - } + ): Intent = MainIntent.createExploreShowIntent(context, exploreName, sourceUrl, exploreUrl) } private val viewModel by viewModel() private val routeEvents = MutableSharedFlow(extraBufferCapacity = 1) private var bookInfoVariableSetter: ((String, String?) -> Unit)? = null - @Serializable - private sealed interface MainRoute : NavKey - - @Serializable - private data object MainRouteHome : MainRoute - - @Serializable - private data object MainRouteSettings : MainRoute - - @Serializable - private data object MainRouteSettingsOther : MainRoute - - @Serializable - private data object MainRouteSettingsRead : MainRoute - - @Serializable - private data object MainRouteSettingsCover : MainRoute - - @Serializable - private data object MainRouteSettingsTheme : MainRoute - - @Serializable - private data object MainRouteSettingsBackup : MainRoute - - @Serializable - private data object MainRouteSettingsCustomTheme : MainRoute - - @Serializable - private data object MainRouteSettingsThemeManage : MainRoute - - @Serializable - private data object MainRouteSettingsDownloadCache : MainRoute - - @Serializable - private data object MainRouteImportLocal : MainRoute - - @Serializable - private data object MainRouteImportRemote : MainRoute - - @Serializable - private data object MainRouteReadRecord : MainRoute - - @Serializable - private data object MainRouteReadRecordOverview : MainRoute - - @Serializable - private data class MainRouteCache(val groupId: Long) : MainRoute - - @Serializable - private data object MainRouteBookCacheManage : MainRoute - - @Serializable - private data class MainRouteSearch( - val key: String?, - val scopeRaw: String? = null - ) : MainRoute - - @Serializable - private data class MainRouteBookInfo( - val name: String?, - val author: String?, - val bookUrl: String, - ) : MainRoute - - @Serializable - private data class MainRouteExploreShow( - val title: String?, - val sourceUrl: String, - val exploreUrl: String?, - ) : MainRoute - override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() super.onCreate(savedInstanceState) @@ -367,7 +145,7 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback { override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) - routeEvents.tryEmit(resolveStartRoute(intent)) + routeEvents.tryEmit(MainNavigator.resolveStartRoute(intent)) } @OptIn(ExperimentalSharedTransitionApi::class) @@ -385,11 +163,11 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback { else -> false } - val backStack = rememberNavBackStack(resolveStartRoute(intent)) + val backStack = rememberNavBackStack(MainNavigator.resolveStartRoute(intent)) LaunchedEffect(backStack) { routeEvents.collect { route -> - navigateToRoute(backStack, route) + MainNavigator.navigateToRoute(backStack, route) } } @@ -397,556 +175,69 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback { NavDisplay( backStack = backStack, transitionSpec = { - (slideIntoContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Start, - animationSpec = tween( - durationMillis = 480, - easing = FastOutSlowInEasing - ), - initialOffset = { fullWidth -> fullWidth } - ) + fadeIn( - animationSpec = tween( - durationMillis = 360, - easing = LinearOutSlowInEasing - ) - )) togetherWith (slideOutOfContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Start, - animationSpec = tween( - durationMillis = 480, - easing = FastOutSlowInEasing - ), - targetOffset = { fullWidth -> fullWidth / 4 } - ) + fadeOut( - animationSpec = tween( - durationMillis = 360, - easing = LinearOutSlowInEasing - ) - )) - }, - popTransitionSpec = { - (slideIntoContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Start, - animationSpec = tween( - durationMillis = 480, - easing = FastOutSlowInEasing - ), - initialOffset = { fullWidth -> -fullWidth / 4 } - ) + fadeIn( - animationSpec = tween( - durationMillis = 360, - easing = LinearOutSlowInEasing - ) - )) togetherWith (scaleOut( - targetScale = 0.8f, - animationSpec = tween( - durationMillis = 480, - easing = FastOutSlowInEasing - ) - ) + fadeOut( - animationSpec = tween(durationMillis = 360) - )) - }, - predictivePopTransitionSpec = { _ -> - (slideIntoContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Start, - animationSpec = tween( - easing = FastOutSlowInEasing - ), - initialOffset = { fullWidth -> -fullWidth / 4 } - ) + fadeIn( - animationSpec = tween( - easing = LinearOutSlowInEasing - ) - )) togetherWith (scaleOut( - targetScale = 0.8f, - animationSpec = tween( - easing = FastOutSlowInEasing - ) - ) + fadeOut( - animationSpec = tween() - )) - }, - onBack = { - if (backStack.size > 1) { - backStack.removeLastOrNull() - } else { - finish() - } - }, - entryProvider = entryProvider { - entry { - MainScreen( - useRail = useRail, - onOpenSettings = { - navigateToRoute(backStack, MainRouteSettings) - }, - onNavigateToSearch = { key -> - navigateToRoute( - backStack, - MainRouteSearch( - key = key?.trim()?.takeIf { it.isNotEmpty() } - ) - ) - }, - onNavigateToRemoteImport = { - navigateToRoute(backStack, MainRouteImportRemote) - }, - onNavigateToLocalImport = { - navigateToRoute(backStack, MainRouteImportLocal) - }, - onNavigateToCache = { groupId -> - navigateToRoute(backStack, MainRouteCache(groupId)) - }, - onNavigateToBookCacheManage = { - navigateToRoute(backStack, MainRouteBookCacheManage) - }, - onNavigateToBookInfo = { name, author, bookUrl -> - navigateToRoute( - backStack, - MainRouteBookInfo( - name = name, - author = author, - bookUrl = bookUrl - ) - ) - }, - onNavigateToExploreShow = { title, sourceUrl, exploreUrl -> - navigateToRoute( - backStack, - MainRouteExploreShow( - title = title, - sourceUrl = sourceUrl, - exploreUrl = exploreUrl - ) - ) - }, - onNavigateToRssSort = { sourceUrl, sortUrl, key -> - navigateToRoute( - backStack, - MainRouteRssSort( - sourceUrl = sourceUrl, - sortUrl = sortUrl, - key = key - ) - ) - }, - onNavigateToRssRead = { title, origin, link, openUrl -> - navigateToRoute( - backStack, - MainRouteRssRead( - title = title, - origin = origin, - link = link, - openUrl = openUrl - ) - ) - }, - onNavigateToReadRecord = { - navigateToRoute(backStack, MainRouteReadRecord) - }, - sharedTransitionScope = this@SharedTransitionLayout, - animatedVisibilityScope = LocalNavAnimatedContentScope.current, - ) - } - - entry { - ConfigNavScreen( - onBackClick = { navigateBack(backStack) }, - onNavigateToOther = { backStack.add(MainRouteSettingsOther) }, - onNavigateToRead = { backStack.add(MainRouteSettingsRead) }, - onNavigateToCover = { backStack.add(MainRouteSettingsCover) }, - onNavigateToTheme = { backStack.add(MainRouteSettingsTheme) }, - onNavigateToBackup = { backStack.add(MainRouteSettingsBackup) }, - onNavigateToDownloadCache = { backStack.add(MainRouteSettingsDownloadCache) } - ) - } - - entry { - OtherConfigScreen(onBackClick = { navigateBack(backStack) }) - } - - entry { - ReadConfigScreen(onBackClick = { navigateBack(backStack) }) - } - - entry { - CoverConfigScreen(onBackClick = { navigateBack(backStack) }) - } - - entry { - ThemeConfigScreen( - onBackClick = { navigateBack(backStack) }, - onNavigateToCustomTheme = { backStack.add(MainRouteSettingsCustomTheme) }, - onNavigateToThemeManage = { backStack.add(MainRouteSettingsThemeManage) } - ) - } - - entry { - BackupConfigScreen(onBackClick = { navigateBack(backStack) }) - } - - entry { - DownloadCacheConfigScreen(onBackClick = { navigateBack(backStack) }) - } - - entry { - CustomThemeScreen( - onBackClick = { navigateBack(backStack) } - ) - } - - entry { - ThemeManageScreen(onBackClick = { navigateBack(backStack) }) - } - - entry { - ImportBookScreen( - onBackClick = { navigateBack(backStack) } - ) - } - - entry { - RemoteBookScreen( - onBackClick = { navigateBack(backStack) } - ) - } - - entry { route -> - BookshelfManageRouteScreen( - groupId = route.groupId, - onBackClick = { navigateBack(backStack) }, - onOpenBookInfo = { name, author, bookUrl -> - navigateToRoute( - backStack, - MainRouteBookInfo( - name = name, - author = author, - bookUrl = bookUrl - ) - ) - } - ) - } - - entry { - BookCacheManageRouteScreen( - onBackClick = { navigateBack(backStack) } - ) - } - - entry { route -> - val searchViewModel = koinViewModel() - - LaunchedEffect(route.key, route.scopeRaw, searchViewModel) { - searchViewModel.onIntent( - SearchIntent.Initialize( - key = route.key, - scopeRaw = route.scopeRaw - ) + (slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Start, + animationSpec = tween(durationMillis = 480, easing = FastOutSlowInEasing), + initialOffset = { fullWidth -> fullWidth } + ) + fadeIn( + animationSpec = tween( + durationMillis = 360, + easing = LinearOutSlowInEasing ) - } - - SearchScreen( - viewModel = searchViewModel, - onBack = { - searchViewModel.onIntent(SearchIntent.ClearSearchResults) - navigateBack(backStack) - }, - onOpenBookInfo = { name, author, bookUrl -> - navigateToRoute( - backStack, - MainRouteBookInfo( - name = name, - author = author, - bookUrl = bookUrl - ) - ) - }, - onOpenSourceManage = { - this@MainActivity.startActivity() - }, - sharedTransitionScope = this@SharedTransitionLayout, - animatedVisibilityScope = LocalNavAnimatedContentScope.current, - ) - } - - entry { route -> - RssSortRouteScreen( - sourceUrl = route.sourceUrl, - initialSortUrl = route.sortUrl, - onBackClick = { navigateBack(backStack) }, - onOpenRead = { title, origin, link, openUrl -> - if (link?.contains("@js:") == true) { - navigateToRoute( - backStack, - MainRouteRssSort( - sourceUrl = origin, - sortUrl = link - ) - ) - } else { - navigateToRoute( - backStack, - MainRouteRssRead( - title = title, - origin = origin, - link = link, - openUrl = openUrl - ) - ) - } - } - ) - } - - entry { route -> - RssReadRouteScreen( - title = route.title, - origin = route.origin, - link = route.link, - openUrl = route.openUrl, - onBackClick = { navigateBack(backStack) } - ) - } - - entry { - ReadRecordScreen( - onBackClick = { navigateBack(backStack) }, - onBookClick = { name, author -> - lifecycleScope.launch { - val book = withContext(IO) { - io.legado.app.data.appDb.bookDao.getBook(name, author) - } - if (book != null) this@MainActivity.startActivityForBook(book) - else { - navigateToRoute(backStack, MainRouteSearch(key = name)) - } - } - }, - onSummaryClick = { - navigateToRoute(backStack, MainRouteReadRecordOverview) - } - ) - } - - entry { - ReadRecordOverviewScreen( - onBackClick = { navigateBack(backStack) }, - onBookClick = { name, author -> - lifecycleScope.launch { - val book = withContext(IO) { - io.legado.app.data.appDb.bookDao.getBook(name, author) - } - if (book != null) this@MainActivity.startActivityForBook(book) - else { - navigateToRoute(backStack, MainRouteSearch(key = name)) - } - } - } - ) - } - - entry( - metadata = NavDisplay.transitionSpec { - val from = initialState.key - val fromStr = from.toString() - if (from is MainRouteHome || from is MainRouteExploreShow || from is MainRouteSearch || - fromStr.startsWith("MainRouteHome") || fromStr.startsWith("MainRouteExploreShow") || fromStr.startsWith("MainRouteSearch")) { - fadeIn(animationSpec = tween(300)) togetherWith - fadeOut(animationSpec = tween(300)) - } else null - } + NavDisplay.popTransitionSpec { - val to = targetState.key - val toStr = to.toString() - if (to is MainRouteHome || to is MainRouteExploreShow || to is MainRouteSearch || - toStr.startsWith("MainRouteHome") || toStr.startsWith("MainRouteExploreShow") || toStr.startsWith("MainRouteSearch")) { - fadeIn(animationSpec = tween(300)) togetherWith - fadeOut(animationSpec = tween(300)) - } else null - } + NavDisplay.predictivePopTransitionSpec { _ -> - val to = targetState.key - val toStr = to.toString() - if (to is MainRouteHome || to is MainRouteExploreShow || to is MainRouteSearch || - toStr.startsWith("MainRouteHome") || toStr.startsWith("MainRouteExploreShow") || toStr.startsWith("MainRouteSearch")) { - fadeIn(animationSpec = tween(300)) togetherWith - fadeOut(animationSpec = tween(300)) - } else null - } - ) { route -> - val bookInfoViewModel = koinViewModel() - BookInfoRouteScreen( - bookUrl = route.bookUrl, - viewModel = bookInfoViewModel, - onBack = { navigateBack(backStack) }, - onFinish = { _, _ -> navigateBack(backStack) }, - onOpenSearch = { keyword -> - navigateToRoute(backStack, MainRouteSearch(key = keyword)) - }, - sharedTransitionScope = this@SharedTransitionLayout, - animatedVisibilityScope = LocalNavAnimatedContentScope.current, - sharedCoverKey = bookCoverSharedElementKey(route.bookUrl), - onRegisterVariableSetter = { setter -> - bookInfoVariableSetter = setter - } - ) - } - - entry { route -> - ExploreShowScreen( - title = route.title ?: "探索", - sourceUrl = route.sourceUrl, - exploreUrl = route.exploreUrl, - onBack = { navigateBack(backStack) }, - onBookClick = { book -> - navigateToRoute( - backStack, - MainRouteBookInfo( - name = book.name, - author = book.author, - bookUrl = book.bookUrl - ) - ) - }, - sharedTransitionScope = this@SharedTransitionLayout, - animatedVisibilityScope = LocalNavAnimatedContentScope.current, - ) - } - } + )) togetherWith (slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Start, + animationSpec = tween(durationMillis = 480, easing = FastOutSlowInEasing), + targetOffset = { fullWidth -> fullWidth / 4 } + ) + fadeOut( + animationSpec = tween( + durationMillis = 360, + easing = LinearOutSlowInEasing + ) + )) + }, + popTransitionSpec = { + (slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Start, + animationSpec = tween(durationMillis = 480, easing = FastOutSlowInEasing), + initialOffset = { fullWidth -> -fullWidth / 4 } + ) + fadeIn( + animationSpec = tween( + durationMillis = 360, + easing = LinearOutSlowInEasing + ) + )) togetherWith (scaleOut( + targetScale = 0.8f, + animationSpec = tween(durationMillis = 480, easing = FastOutSlowInEasing) + ) + fadeOut(animationSpec = tween(durationMillis = 360))) + }, + predictivePopTransitionSpec = { _ -> + (slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Start, + animationSpec = tween(easing = FastOutSlowInEasing), + initialOffset = { fullWidth -> -fullWidth / 4 } + ) + fadeIn(animationSpec = tween(easing = LinearOutSlowInEasing))) togetherWith (scaleOut( + targetScale = 0.8f, + animationSpec = tween(easing = FastOutSlowInEasing) + ) + fadeOut(animationSpec = tween())) + }, + onBack = { MainNavigator.navigateBack(this@MainActivity, backStack) }, + entryProvider = mainEntryProvider( + backStack = backStack, + useRail = useRail, + sharedTransitionScope = this@SharedTransitionLayout, + onNavigateToRoute = { route -> + MainNavigator.navigateToRoute( + backStack, + route + ) + }, + onNavigateBack = { MainNavigator.navigateBack(this@MainActivity, backStack) }, + onRegisterVariableSetter = { setter -> bookInfoVariableSetter = setter } + ) ) } } - private fun navigateToRoute(backStack: MutableList, route: NavKey) { - val currentRoute = backStack.lastOrNull() - if (currentRoute == route) return - - when (route) { - MainRouteHome -> { - backStack.clear() - backStack.add(MainRouteHome) - } - - MainRouteSettings -> { - if (currentRoute == MainRouteHome) { - backStack.add(MainRouteSettings) - } else { - backStack.clear() - backStack.add(MainRouteHome) - backStack.add(MainRouteSettings) - } - } - - MainRouteSettingsOther, - MainRouteSettingsRead, - MainRouteSettingsCover, - MainRouteSettingsTheme, - MainRouteSettingsBackup, - MainRouteSettingsCustomTheme, - MainRouteSettingsThemeManage, - MainRouteSettingsDownloadCache -> { - backStack.clear() - backStack.add(MainRouteHome) - backStack.add(MainRouteSettings) - backStack.add(route) - } - - MainRouteImportLocal, - MainRouteImportRemote, - is MainRouteCache, - MainRouteBookCacheManage -> { - if (currentRoute == MainRouteHome) { - backStack.add(route) - } else { - backStack.clear() - backStack.add(MainRouteHome) - backStack.add(route) - } - } - - is MainRouteSearch -> { - if (currentRoute == MainRouteHome) { - backStack.add(route) - } else { - backStack.clear() - backStack.add(MainRouteHome) - backStack.add(route) - } - } - - is MainRouteBookInfo -> { - if ( - currentRoute == MainRouteHome || - currentRoute is MainRouteSearch || - currentRoute is MainRouteExploreShow - ) { - backStack.add(route) - } else { - backStack.clear() - backStack.add(MainRouteHome) - backStack.add(route) - } - } - - is MainRouteExploreShow -> { - if (currentRoute == MainRouteHome) { - backStack.add(route) - } else { - backStack.clear() - backStack.add(MainRouteHome) - backStack.add(route) - } - } - - is MainRouteRssSort -> { - if (currentRoute == MainRouteHome) { - backStack.add(route) - } else { - backStack.clear() - backStack.add(MainRouteHome) - backStack.add(route) - } - } - - is MainRouteRssRead -> { - if (currentRoute == MainRouteHome || currentRoute is MainRouteRssSort) { - backStack.add(route) - } else { - backStack.clear() - backStack.add(MainRouteHome) - backStack.add(route) - } - } - - MainRouteReadRecord -> { - if (currentRoute == MainRouteHome) { - backStack.add(route) - } else { - backStack.clear() - backStack.add(MainRouteHome) - backStack.add(route) - } - } - - MainRouteReadRecordOverview -> { - if (currentRoute == MainRouteHome || currentRoute == MainRouteReadRecord) { - backStack.add(route) - } else { - backStack.clear() - backStack.add(MainRouteHome) - backStack.add(route) - } - } - } - } - - private fun navigateBack(backStack: MutableList) { - if (backStack.size > 1) { - backStack.removeLastOrNull() - } else { - finish() - } - } - private fun checkStartupRoute(): Boolean { return when { LocalConfig.isFirstOpenApp -> { @@ -1057,96 +348,6 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback { } } - private fun resolveStartRoute(intent: Intent?): NavKey { - val route = intent?.getStringExtra(EXTRA_START_ROUTE) - resolveRssStartRoute(route, intent)?.let { return it } - return resolveStartRoute(route) - } - - private fun resolveRssStartRoute(route: String?, intent: Intent?): NavKey? { - return when (route) { - ROUTE_RSS_SORT -> { - val sourceUrl = intent?.getStringExtra(EXTRA_RSS_SOURCE_URL) - if (sourceUrl.isNullOrBlank()) { - null - } else { - MainRouteRssSort( - sourceUrl = sourceUrl, - sortUrl = intent.getStringExtra(EXTRA_RSS_SORT_URL), - key = intent.getStringExtra(EXTRA_RSS_KEY) - ) - } - } - - ROUTE_RSS_READ -> { - val origin = intent?.getStringExtra(EXTRA_RSS_READ_ORIGIN) - if (origin.isNullOrBlank()) { - null - } else { - MainRouteRssRead( - title = intent.getStringExtra(EXTRA_RSS_READ_TITLE), - origin = origin, - link = intent.getStringExtra(EXTRA_RSS_READ_LINK), - openUrl = intent.getStringExtra(EXTRA_RSS_READ_OPEN_URL) - ) - } - } - - else -> null - } - } - - private fun resolveStartRoute(route: String?): MainRoute { - return when (route) { - "main" -> MainRouteHome - "settings" -> MainRouteSettings - "settings/other" -> MainRouteSettingsOther - "settings/read" -> MainRouteSettingsRead - "settings/cover" -> MainRouteSettingsCover - "settings/theme" -> MainRouteSettingsTheme - "settings/backup" -> MainRouteSettingsBackup - "settings/custom_theme" -> MainRouteSettingsCustomTheme - "settings/download_cache" -> MainRouteSettingsDownloadCache - "import/local" -> MainRouteImportLocal - "import/remote" -> MainRouteImportRemote - ROUTE_MAIN -> MainRouteHome - ROUTE_SETTINGS -> MainRouteSettings - ROUTE_SETTINGS_OTHER -> MainRouteSettingsOther - ROUTE_SETTINGS_READ -> MainRouteSettingsRead - ROUTE_SETTINGS_COVER -> MainRouteSettingsCover - ROUTE_SETTINGS_THEME -> MainRouteSettingsTheme - ROUTE_SETTINGS_BACKUP -> MainRouteSettingsBackup - ROUTE_SETTINGS_DOWNLOAD_CACHE -> MainRouteSettingsDownloadCache - ROUTE_IMPORT_LOCAL -> MainRouteImportLocal - ROUTE_IMPORT_REMOTE -> MainRouteImportRemote - ROUTE_CACHE -> MainRouteCache(intent?.getLongExtra(EXTRA_CACHE_GROUP_ID, -1L) ?: -1L) - ROUTE_BOOK_CACHE_MANAGE -> MainRouteBookCacheManage - ROUTE_SEARCH -> MainRouteSearch( - key = intent?.getStringExtra(EXTRA_SEARCH_KEY), - scopeRaw = intent?.getStringExtra(EXTRA_SEARCH_SCOPE) - ) - ROUTE_BOOK_INFO -> intent?.getStringExtra(EXTRA_BOOK_URL) - ?.takeIf { it.isNotBlank() } - ?.let { bookUrl -> - MainRouteBookInfo( - name = intent.getStringExtra(EXTRA_BOOK_NAME), - author = intent.getStringExtra(EXTRA_BOOK_AUTHOR), - bookUrl = bookUrl - ) - } ?: MainRouteHome - ROUTE_EXPLORE_SHOW -> intent?.getStringExtra(EXTRA_SOURCE_URL) - ?.takeIf { it.isNotBlank() } - ?.let { sourceUrl -> - MainRouteExploreShow( - title = intent.getStringExtra(EXTRA_EXPLORE_NAME), - sourceUrl = sourceUrl, - exploreUrl = intent.getStringExtra(EXTRA_EXPLORE_URL) - ) - } ?: MainRouteHome - else -> MainRouteHome - } - } - override fun setVariable(key: String, variable: String?) { bookInfoVariableSetter?.invoke(key, variable) } diff --git a/app/src/main/java/io/legado/app/ui/main/MainIntent.kt b/app/src/main/java/io/legado/app/ui/main/MainIntent.kt new file mode 100644 index 000000000..24e769d93 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/main/MainIntent.kt @@ -0,0 +1,152 @@ +package io.legado.app.ui.main + +import android.content.Context +import android.content.Intent +import io.legado.app.ui.config.ConfigTag + +object MainIntent { + const val EXTRA_START_ROUTE = "startRoute" + const val EXTRA_CACHE_GROUP_ID = "extra_cache_group_id" + const val EXTRA_SEARCH_KEY = "extra_search_key" + const val EXTRA_SEARCH_SCOPE = "extra_search_scope" + const val EXTRA_BOOK_NAME = "name" + const val EXTRA_BOOK_AUTHOR = "author" + const val EXTRA_BOOK_URL = "bookUrl" + const val EXTRA_EXPLORE_NAME = "exploreName" + const val EXTRA_SOURCE_URL = "sourceUrl" + const val EXTRA_EXPLORE_URL = "exploreUrl" + + const val EXTRA_RSS_SOURCE_URL = "extra_rss_source_url" + const val EXTRA_RSS_SORT_URL = "extra_rss_sort_url" + const val EXTRA_RSS_KEY = "extra_rss_key" + + const val EXTRA_RSS_READ_TITLE = "extra_rss_read_title" + const val EXTRA_RSS_READ_ORIGIN = "extra_rss_read_origin" + const val EXTRA_RSS_READ_LINK = "extra_rss_read_link" + const val EXTRA_RSS_READ_OPEN_URL = "extra_rss_read_open_url" + + fun createLauncherIntent(context: Context): Intent { + val launcherComponent = + context.packageManager.getLaunchIntentForPackage(context.packageName)?.component + return if (launcherComponent != null) { + Intent().setComponent(launcherComponent) + } else { + Intent(context, MainActivity::class.java) + } + } + + fun createHomeIntent(context: Context): Intent { + return createLauncherIntent(context).apply { + putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_MAIN) + } + } + + fun createIntent(context: Context, configTag: String? = null): Intent { + return createLauncherIntent(context).apply { + putExtra(EXTRA_START_ROUTE, routeForConfigTag(configTag)) + } + } + + fun createRssSortIntent( + context: Context, + sourceUrl: String, + sortUrl: String? = null, + key: String? = null + ): Intent { + return createLauncherIntent(context).apply { + putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_RSS_SORT) + putExtra(EXTRA_RSS_SOURCE_URL, sourceUrl) + putExtra(EXTRA_RSS_SORT_URL, sortUrl) + putExtra(EXTRA_RSS_KEY, key) + } + } + + fun createRssReadIntent( + context: Context, + title: String? = null, + origin: String, + link: String? = null, + openUrl: String? = null + ): Intent { + return createLauncherIntent(context).apply { + putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_RSS_READ) + putExtra(EXTRA_RSS_READ_TITLE, title) + putExtra(EXTRA_RSS_READ_ORIGIN, origin) + putExtra(EXTRA_RSS_READ_LINK, link) + putExtra(EXTRA_RSS_READ_OPEN_URL, openUrl) + } + } + + fun createBookshelfManageScreenIntent( + context: Context, + groupId: Long = -1L + ): Intent { + return createLauncherIntent(context).apply { + putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_CACHE) + putExtra(EXTRA_CACHE_GROUP_ID, groupId) + } + } + + fun createCacheIntent( + context: Context, + groupId: Long = -1L + ): Intent = createBookshelfManageScreenIntent(context, groupId) + + fun createBookCacheManageIntent(context: Context): Intent { + return createLauncherIntent(context).apply { + putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_BOOK_CACHE_MANAGE) + } + } + + fun createSearchIntent( + context: Context, + key: String? = null, + scopeRaw: String? = null + ): Intent { + return createLauncherIntent(context).apply { + putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_SEARCH) + putExtra(EXTRA_SEARCH_KEY, key) + putExtra(EXTRA_SEARCH_SCOPE, scopeRaw) + } + } + + fun createBookInfoIntent( + context: Context, + name: String? = null, + author: String? = null, + bookUrl: String + ): Intent { + return createLauncherIntent(context).apply { + putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_BOOK_INFO) + putExtra(EXTRA_BOOK_NAME, name) + putExtra(EXTRA_BOOK_AUTHOR, author) + putExtra(EXTRA_BOOK_URL, bookUrl) + } + } + + fun createExploreShowIntent( + context: Context, + exploreName: String? = null, + sourceUrl: String, + exploreUrl: String? = null + ): Intent { + return createLauncherIntent(context).apply { + putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_EXPLORE_SHOW) + putExtra(EXTRA_EXPLORE_NAME, exploreName) + putExtra(EXTRA_SOURCE_URL, sourceUrl) + putExtra(EXTRA_EXPLORE_URL, exploreUrl) + } + } + + private fun routeForConfigTag(configTag: String?): String { + return when (configTag) { + ConfigTag.OTHER_CONFIG -> MainRouteConst.ROUTE_SETTINGS_OTHER + ConfigTag.READ_CONFIG -> MainRouteConst.ROUTE_SETTINGS_READ + ConfigTag.COVER_CONFIG -> MainRouteConst.ROUTE_SETTINGS_COVER + ConfigTag.THEME_CONFIG -> MainRouteConst.ROUTE_SETTINGS_THEME + ConfigTag.BACKUP_CONFIG -> MainRouteConst.ROUTE_SETTINGS_BACKUP + ConfigTag.DOWNLOAD_CACHE_CONFIG -> MainRouteConst.ROUTE_SETTINGS_DOWNLOAD_CACHE + else -> MainRouteConst.ROUTE_SETTINGS + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt b/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt new file mode 100644 index 000000000..43b35bd92 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt @@ -0,0 +1,423 @@ +package io.legado.app.ui.main + +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith +import androidx.compose.runtime.LaunchedEffect +import androidx.lifecycle.lifecycleScope +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.ui.LocalNavAnimatedContentScope +import androidx.navigation3.ui.NavDisplay +import io.legado.app.ui.book.cache.manage.BookCacheManageRouteScreen +import io.legado.app.ui.book.explore.ExploreShowScreen +import io.legado.app.ui.book.import.local.ImportBookScreen +import io.legado.app.ui.book.import.remote.RemoteBookScreen +import io.legado.app.ui.book.info.BookInfoRouteScreen +import io.legado.app.ui.book.info.BookInfoViewModel +import io.legado.app.ui.book.manage.BookshelfManageRouteScreen +import io.legado.app.ui.book.readRecord.ReadRecordOverviewScreen +import io.legado.app.ui.book.readRecord.ReadRecordScreen +import io.legado.app.ui.book.search.SearchIntent +import io.legado.app.ui.book.search.SearchScreen +import io.legado.app.ui.book.search.SearchViewModel +import io.legado.app.ui.book.source.manage.BookSourceActivity +import io.legado.app.ui.config.ConfigNavScreen +import io.legado.app.ui.config.backupConfig.BackupConfigScreen +import io.legado.app.ui.config.coverConfig.CoverConfigScreen +import io.legado.app.ui.config.customTheme.CustomThemeScreen +import io.legado.app.ui.config.downloadCacheConfig.DownloadCacheConfigScreen +import io.legado.app.ui.config.otherConfig.OtherConfigScreen +import io.legado.app.ui.config.readConfig.ReadConfigScreen +import io.legado.app.ui.config.themeConfig.ThemeConfigScreen +import io.legado.app.ui.config.themeManage.ThemeManageScreen +import io.legado.app.ui.rss.article.MainRouteRssSort +import io.legado.app.ui.rss.article.RssSortRouteScreen +import io.legado.app.ui.rss.favorites.RssFavoritesScreen +import io.legado.app.ui.rss.read.MainRouteRssRead +import io.legado.app.ui.rss.read.RssReadRouteScreen +import io.legado.app.ui.rss.subscription.RuleSubScreen +import io.legado.app.utils.startActivity +import io.legado.app.utils.startActivityForBook +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.androidx.compose.koinViewModel + +@OptIn(ExperimentalSharedTransitionApi::class) +fun MainActivity.mainEntryProvider( + backStack: MutableList, + useRail: Boolean, + sharedTransitionScope: SharedTransitionScope, + onNavigateToRoute: (NavKey) -> Unit, + onNavigateBack: () -> Unit, + onRegisterVariableSetter: (((String, String?) -> Unit)?) -> Unit +) = entryProvider { + entry { + MainScreen( + useRail = useRail, + onOpenSettings = { + onNavigateToRoute(MainRouteSettings) + }, + onNavigateToSearch = { key -> + onNavigateToRoute( + MainRouteSearch( + key = key?.trim()?.takeIf { it.isNotEmpty() } + ) + ) + }, + onNavigateToRemoteImport = { + onNavigateToRoute(MainRouteImportRemote) + }, + onNavigateToLocalImport = { + onNavigateToRoute(MainRouteImportLocal) + }, + onNavigateToCache = { groupId -> + onNavigateToRoute(MainRouteCache(groupId)) + }, + onNavigateToBookCacheManage = { + onNavigateToRoute(MainRouteBookCacheManage) + }, + onNavigateToBookInfo = { name, author, bookUrl -> + onNavigateToRoute( + MainRouteBookInfo( + name = name, + author = author, + bookUrl = bookUrl + ) + ) + }, + onNavigateToExploreShow = { title, sourceUrl, exploreUrl -> + onNavigateToRoute( + MainRouteExploreShow( + title = title, + sourceUrl = sourceUrl, + exploreUrl = exploreUrl + ) + ) + }, + onNavigateToRssSort = { sourceUrl, sortUrl, key -> + onNavigateToRoute( + MainRouteRssSort( + sourceUrl = sourceUrl, + sortUrl = sortUrl, + key = key + ) + ) + }, + onNavigateToRssRead = { title, origin, link, openUrl -> + onNavigateToRoute( + MainRouteRssRead( + title = title, + origin = origin, + link = link, + openUrl = openUrl + ) + ) + }, + onNavigateToRssFavorites = { + onNavigateToRoute(MainRouteRssFavorites) + }, + onNavigateToRuleSub = { + onNavigateToRoute(MainRouteRuleSub) + }, + onNavigateToReadRecord = { + onNavigateToRoute(MainRouteReadRecord) + }, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = LocalNavAnimatedContentScope.current, + ) + } + + entry { + ConfigNavScreen( + onBackClick = { onNavigateBack() }, + onNavigateToOther = { backStack.add(MainRouteSettingsOther) }, + onNavigateToRead = { backStack.add(MainRouteSettingsRead) }, + onNavigateToCover = { backStack.add(MainRouteSettingsCover) }, + onNavigateToTheme = { backStack.add(MainRouteSettingsTheme) }, + onNavigateToBackup = { backStack.add(MainRouteSettingsBackup) }, + onNavigateToDownloadCache = { backStack.add(MainRouteSettingsDownloadCache) } + ) + } + + entry { + OtherConfigScreen(onBackClick = { onNavigateBack() }) + } + + entry { + ReadConfigScreen(onBackClick = { onNavigateBack() }) + } + + entry { + CoverConfigScreen(onBackClick = { onNavigateBack() }) + } + + entry { + ThemeConfigScreen( + onBackClick = { onNavigateBack() }, + onNavigateToCustomTheme = { backStack.add(MainRouteSettingsCustomTheme) }, + onNavigateToThemeManage = { backStack.add(MainRouteSettingsThemeManage) } + ) + } + + entry { + BackupConfigScreen(onBackClick = { onNavigateBack() }) + } + + entry { + DownloadCacheConfigScreen(onBackClick = { onNavigateBack() }) + } + + entry { + CustomThemeScreen( + onBackClick = { onNavigateBack() } + ) + } + + entry { + ThemeManageScreen(onBackClick = { onNavigateBack() }) + } + + entry { + ImportBookScreen( + onBackClick = { onNavigateBack() } + ) + } + + entry { + RemoteBookScreen( + onBackClick = { onNavigateBack() } + ) + } + + entry { route -> + BookshelfManageRouteScreen( + groupId = route.groupId, + onBackClick = { onNavigateBack() }, + onOpenBookInfo = { name, author, bookUrl -> + onNavigateToRoute( + MainRouteBookInfo( + name = name, + author = author, + bookUrl = bookUrl + ) + ) + } + ) + } + + entry { + BookCacheManageRouteScreen( + onBackClick = { onNavigateBack() } + ) + } + + entry { route -> + val searchViewModel = koinViewModel() + + LaunchedEffect(route.key, route.scopeRaw, searchViewModel) { + searchViewModel.onIntent( + SearchIntent.Initialize( + key = route.key, + scopeRaw = route.scopeRaw + ) + ) + } + + SearchScreen( + viewModel = searchViewModel, + onBack = { + searchViewModel.onIntent(SearchIntent.ClearSearchResults) + onNavigateBack() + }, + onOpenBookInfo = { name, author, bookUrl -> + onNavigateToRoute( + MainRouteBookInfo( + name = name, + author = author, + bookUrl = bookUrl + ) + ) + }, + onOpenSourceManage = { + this@mainEntryProvider.startActivity() + }, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = LocalNavAnimatedContentScope.current, + ) + } + + entry { route -> + RssSortRouteScreen( + sourceUrl = route.sourceUrl, + initialSortUrl = route.sortUrl, + onBackClick = { onNavigateBack() }, + onOpenRead = { title, origin, link, openUrl -> + if (link?.contains("@js:") == true) { + onNavigateToRoute( + MainRouteRssSort( + sourceUrl = origin, + sortUrl = link + ) + ) + } else { + onNavigateToRoute( + MainRouteRssRead( + title = title, + origin = origin, + link = link, + openUrl = openUrl + ) + ) + } + } + ) + } + + entry { route -> + RssReadRouteScreen( + title = route.title, + origin = route.origin, + link = route.link, + openUrl = route.openUrl, + onBackClick = { onNavigateBack() } + ) + } + + entry { + RssFavoritesScreen( + onBackClick = { onNavigateBack() }, + onOpenRead = { title, origin, link, openUrl -> + onNavigateToRoute( + MainRouteRssRead( + title = title, + origin = origin, + link = link, + openUrl = openUrl + ) + ) + } + ) + } + + entry { + RuleSubScreen( + onBackClick = { onNavigateBack() } + ) + } + + entry { + ReadRecordScreen( + onBackClick = { onNavigateBack() }, + onBookClick = { name, author -> + lifecycleScope.launch { + val book = withContext(IO) { + io.legado.app.data.appDb.bookDao.getBook(name, author) + } + if (book != null) this@mainEntryProvider.startActivityForBook(book) + else { + onNavigateToRoute(MainRouteSearch(key = name)) + } + } + }, + onSummaryClick = { + onNavigateToRoute(MainRouteReadRecordOverview) + } + ) + } + + entry { + ReadRecordOverviewScreen( + onBackClick = { onNavigateBack() }, + onBookClick = { name, author -> + lifecycleScope.launch { + val book = withContext(IO) { + io.legado.app.data.appDb.bookDao.getBook(name, author) + } + if (book != null) this@mainEntryProvider.startActivityForBook(book) + else { + onNavigateToRoute(MainRouteSearch(key = name)) + } + } + } + ) + } + + entry( + metadata = NavDisplay.transitionSpec { + val from = initialState.key + val fromStr = from.toString() + if (from is MainRouteHome || from is MainRouteExploreShow || from is MainRouteSearch || + fromStr.startsWith("MainRouteHome") || fromStr.startsWith("MainRouteExploreShow") || fromStr.startsWith( + "MainRouteSearch" + ) + ) { + fadeIn(animationSpec = tween(300)) togetherWith + fadeOut(animationSpec = tween(300)) + } else null + } + NavDisplay.popTransitionSpec { + val to = targetState.key + val toStr = to.toString() + if (to is MainRouteHome || to is MainRouteExploreShow || to is MainRouteSearch || + toStr.startsWith("MainRouteHome") || toStr.startsWith("MainRouteExploreShow") || toStr.startsWith( + "MainRouteSearch" + ) + ) { + fadeIn(animationSpec = tween(300)) togetherWith + fadeOut(animationSpec = tween(300)) + } else null + } + NavDisplay.predictivePopTransitionSpec { _ -> + val to = targetState.key + val toStr = to.toString() + if (to is MainRouteHome || to is MainRouteExploreShow || to is MainRouteSearch || + toStr.startsWith("MainRouteHome") || toStr.startsWith("MainRouteExploreShow") || toStr.startsWith( + "MainRouteSearch" + ) + ) { + fadeIn(animationSpec = tween(300)) togetherWith + fadeOut(animationSpec = tween(300)) + } else null + } + ) { route -> + val bookInfoViewModel = koinViewModel() + BookInfoRouteScreen( + bookUrl = route.bookUrl, + viewModel = bookInfoViewModel, + onBack = { onNavigateBack() }, + onFinish = { _, _ -> onNavigateBack() }, + onOpenSearch = { keyword -> + onNavigateToRoute(MainRouteSearch(key = keyword)) + }, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = LocalNavAnimatedContentScope.current, + sharedCoverKey = bookCoverSharedElementKey(route.bookUrl), + onRegisterVariableSetter = { setter -> + onRegisterVariableSetter(setter) + } + ) + } + + entry { route -> + ExploreShowScreen( + title = route.title ?: "探索", + sourceUrl = route.sourceUrl, + exploreUrl = route.exploreUrl, + onBack = { onNavigateBack() }, + onBookClick = { book -> + onNavigateToRoute( + MainRouteBookInfo( + name = book.name, + author = book.author, + bookUrl = book.bookUrl + ) + ) + }, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = LocalNavAnimatedContentScope.current, + ) + } +} diff --git a/app/src/main/java/io/legado/app/ui/main/MainNavKey.kt b/app/src/main/java/io/legado/app/ui/main/MainNavKey.kt new file mode 100644 index 000000000..eb170274b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/main/MainNavKey.kt @@ -0,0 +1,106 @@ +package io.legado.app.ui.main + +import androidx.navigation3.runtime.NavKey +import kotlinx.serialization.Serializable + +@Serializable +sealed interface MainRoute : NavKey + +@Serializable +data object MainRouteHome : MainRoute + +@Serializable +data object MainRouteSettings : MainRoute + +@Serializable +data object MainRouteSettingsOther : MainRoute + +@Serializable +data object MainRouteSettingsRead : MainRoute + +@Serializable +data object MainRouteSettingsCover : MainRoute + +@Serializable +data object MainRouteSettingsTheme : MainRoute + +@Serializable +data object MainRouteSettingsBackup : MainRoute + +@Serializable +data object MainRouteSettingsCustomTheme : MainRoute + +@Serializable +data object MainRouteSettingsThemeManage : MainRoute + +@Serializable +data object MainRouteSettingsDownloadCache : MainRoute + +@Serializable +data object MainRouteImportLocal : MainRoute + +@Serializable +data object MainRouteImportRemote : MainRoute + +@Serializable +data object MainRouteReadRecord : MainRoute + +@Serializable +data object MainRouteReadRecordOverview : MainRoute + +@Serializable +data class MainRouteCache(val groupId: Long) : MainRoute + +@Serializable +data object MainRouteBookCacheManage : MainRoute + +@Serializable +data class MainRouteSearch( + val key: String?, + val scopeRaw: String? = null +) : MainRoute + +@Serializable +data class MainRouteBookInfo( + val name: String?, + val author: String?, + val bookUrl: String, +) : MainRoute + +@Serializable +data object MainRouteRssFavorites : MainRoute + +@Serializable +data object MainRouteRuleSub : MainRoute + +@Serializable +data class MainRouteExploreShow( + val title: String?, + val sourceUrl: String, + val exploreUrl: String?, +) : MainRoute + +object MainRouteConst { + const val ROUTE_MAIN = "main" + const val ROUTE_SETTINGS = "settings" + const val ROUTE_SETTINGS_OTHER = "settings/other" + const val ROUTE_SETTINGS_READ = "settings/read" + const val ROUTE_SETTINGS_COVER = "settings/cover" + const val ROUTE_SETTINGS_THEME = "settings/theme" + const val ROUTE_SETTINGS_BACKUP = "settings/backup" + const val ROUTE_SETTINGS_CUSTOM_THEME = "settings/custom_theme" + const val ROUTE_SETTINGS_DOWNLOAD_CACHE = "settings/download_cache" + const val ROUTE_IMPORT_LOCAL = "import/local" + const val ROUTE_IMPORT_REMOTE = "import/remote" + const val ROUTE_CACHE = "cache" + const val ROUTE_BOOK_CACHE_MANAGE = "book/cache/manage" + const val ROUTE_SEARCH = "search" + const val ROUTE_BOOK_INFO = "book/info" + const val ROUTE_EXPLORE_SHOW = "explore/show" + const val ROUTE_RSS_SORT = "rss/sort" + const val ROUTE_RSS_READ = "rss/read" + const val ROUTE_RSS_FAVORITES = "rss/favorites" + const val ROUTE_RULE_SUB = "rss/rule_sub" + const val ROUTE_READ_RECORD = "read_record" + const val ROUTE_READ_RECORD_OVERVIEW = "read_record_overview" +} diff --git a/app/src/main/java/io/legado/app/ui/main/MainNavigator.kt b/app/src/main/java/io/legado/app/ui/main/MainNavigator.kt new file mode 100644 index 000000000..ae71e697e --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/main/MainNavigator.kt @@ -0,0 +1,253 @@ +package io.legado.app.ui.main + +import android.app.Activity +import android.content.Intent +import androidx.navigation3.runtime.NavKey +import io.legado.app.ui.rss.article.MainRouteRssSort +import io.legado.app.ui.rss.read.MainRouteRssRead + +object MainNavigator { + + fun navigateToRoute(backStack: MutableList, route: NavKey) { + val currentRoute = backStack.lastOrNull() + if (currentRoute == route) return + + when (route) { + MainRouteHome -> { + backStack.clear() + backStack.add(MainRouteHome) + } + + MainRouteSettings -> { + if (currentRoute == MainRouteHome) { + backStack.add(MainRouteSettings) + } else { + backStack.clear() + backStack.add(MainRouteHome) + backStack.add(MainRouteSettings) + } + } + + MainRouteSettingsOther, + MainRouteSettingsRead, + MainRouteSettingsCover, + MainRouteSettingsTheme, + MainRouteSettingsBackup, + MainRouteSettingsCustomTheme, + MainRouteSettingsThemeManage, + MainRouteSettingsDownloadCache -> { + backStack.clear() + backStack.add(MainRouteHome) + backStack.add(MainRouteSettings) + backStack.add(route) + } + + MainRouteImportLocal, + MainRouteImportRemote, + is MainRouteCache, + MainRouteBookCacheManage -> { + if (currentRoute == MainRouteHome) { + backStack.add(route) + } else { + backStack.clear() + backStack.add(MainRouteHome) + backStack.add(route) + } + } + + is MainRouteSearch -> { + if ( + currentRoute == MainRouteHome || + currentRoute is MainRouteBookInfo || + currentRoute is MainRouteExploreShow || + currentRoute is MainRouteSearch + ) { + backStack.add(route) + } else { + backStack.clear() + backStack.add(MainRouteHome) + backStack.add(route) + } + } + + is MainRouteBookInfo -> { + if ( + currentRoute == MainRouteHome || + currentRoute is MainRouteSearch || + currentRoute is MainRouteExploreShow || + currentRoute is MainRouteBookInfo + ) { + backStack.add(route) + } else { + backStack.clear() + backStack.add(MainRouteHome) + backStack.add(route) + } + } + + is MainRouteExploreShow -> { + if ( + currentRoute == MainRouteHome || + currentRoute is MainRouteBookInfo || + currentRoute is MainRouteSearch || + currentRoute is MainRouteExploreShow + ) { + backStack.add(route) + } else { + backStack.clear() + backStack.add(MainRouteHome) + backStack.add(route) + } + } + + is MainRouteRssSort -> { + if ( + currentRoute == MainRouteHome || + currentRoute is MainRouteRssSort || + currentRoute is MainRouteRssRead + ) { + backStack.add(route) + } else { + backStack.clear() + backStack.add(MainRouteHome) + backStack.add(route) + } + } + + is MainRouteRssRead -> { + if ( + currentRoute == MainRouteHome || + currentRoute is MainRouteRssSort || + currentRoute is MainRouteRssRead + ) { + backStack.add(route) + } else { + backStack.clear() + backStack.add(MainRouteHome) + backStack.add(route) + } + } + + MainRouteReadRecord -> { + if (currentRoute == MainRouteHome) { + backStack.add(route) + } else { + backStack.clear() + backStack.add(MainRouteHome) + backStack.add(route) + } + } + + MainRouteReadRecordOverview -> { + if (currentRoute == MainRouteHome || currentRoute == MainRouteReadRecord) { + backStack.add(route) + } else { + backStack.clear() + backStack.add(MainRouteHome) + backStack.add(route) + } + } + } + } + + fun navigateBack(activity: Activity, backStack: MutableList) { + if (backStack.size > 1) { + backStack.removeLastOrNull() + } else { + activity.finish() + } + } + + fun resolveStartRoute(intent: Intent?): NavKey { + val route = intent?.getStringExtra(MainIntent.EXTRA_START_ROUTE) + resolveRssStartRoute(route, intent)?.let { return it } + return resolveStartRoute(route, intent) + } + + private fun resolveRssStartRoute(route: String?, intent: Intent?): NavKey? { + return when (route) { + MainRouteConst.ROUTE_RSS_SORT -> { + val sourceUrl = intent?.getStringExtra(MainIntent.EXTRA_RSS_SOURCE_URL) + if (sourceUrl.isNullOrBlank()) { + null + } else { + MainRouteRssSort( + sourceUrl = sourceUrl, + sortUrl = intent.getStringExtra(MainIntent.EXTRA_RSS_SORT_URL), + key = intent.getStringExtra(MainIntent.EXTRA_RSS_KEY) + ) + } + } + + MainRouteConst.ROUTE_RSS_READ -> { + val origin = intent?.getStringExtra(MainIntent.EXTRA_RSS_READ_ORIGIN) + if (origin.isNullOrBlank()) { + null + } else { + MainRouteRssRead( + title = intent.getStringExtra(MainIntent.EXTRA_RSS_READ_TITLE), + origin = origin, + link = intent.getStringExtra(MainIntent.EXTRA_RSS_READ_LINK), + openUrl = intent.getStringExtra(MainIntent.EXTRA_RSS_READ_OPEN_URL) + ) + } + } + + MainRouteConst.ROUTE_RSS_FAVORITES -> MainRouteRssFavorites + + MainRouteConst.ROUTE_RULE_SUB -> MainRouteRuleSub + + else -> null + } + } + + private fun resolveStartRoute(route: String?, intent: Intent?): MainRoute { + return when (route) { + MainRouteConst.ROUTE_MAIN -> MainRouteHome + MainRouteConst.ROUTE_SETTINGS -> MainRouteSettings + MainRouteConst.ROUTE_SETTINGS_OTHER -> MainRouteSettingsOther + MainRouteConst.ROUTE_SETTINGS_READ -> MainRouteSettingsRead + MainRouteConst.ROUTE_SETTINGS_COVER -> MainRouteSettingsCover + MainRouteConst.ROUTE_SETTINGS_THEME -> MainRouteSettingsTheme + MainRouteConst.ROUTE_SETTINGS_BACKUP -> MainRouteSettingsBackup + MainRouteConst.ROUTE_SETTINGS_CUSTOM_THEME -> MainRouteSettingsCustomTheme + MainRouteConst.ROUTE_SETTINGS_DOWNLOAD_CACHE -> MainRouteSettingsDownloadCache + MainRouteConst.ROUTE_IMPORT_LOCAL -> MainRouteImportLocal + MainRouteConst.ROUTE_IMPORT_REMOTE -> MainRouteImportRemote + MainRouteConst.ROUTE_CACHE -> MainRouteCache( + intent?.getLongExtra( + MainIntent.EXTRA_CACHE_GROUP_ID, + -1L + ) ?: -1L + ) + + MainRouteConst.ROUTE_BOOK_CACHE_MANAGE -> MainRouteBookCacheManage + MainRouteConst.ROUTE_SEARCH -> MainRouteSearch( + key = intent?.getStringExtra(MainIntent.EXTRA_SEARCH_KEY), + scopeRaw = intent?.getStringExtra(MainIntent.EXTRA_SEARCH_SCOPE) + ) + + MainRouteConst.ROUTE_BOOK_INFO -> intent?.getStringExtra(MainIntent.EXTRA_BOOK_URL) + ?.takeIf { it.isNotBlank() } + ?.let { bookUrl -> + MainRouteBookInfo( + name = intent.getStringExtra(MainIntent.EXTRA_BOOK_NAME), + author = intent.getStringExtra(MainIntent.EXTRA_BOOK_AUTHOR), + bookUrl = bookUrl + ) + } ?: MainRouteHome + + MainRouteConst.ROUTE_EXPLORE_SHOW -> intent?.getStringExtra(MainIntent.EXTRA_SOURCE_URL) + ?.takeIf { it.isNotBlank() } + ?.let { sourceUrl -> + MainRouteExploreShow( + title = intent.getStringExtra(MainIntent.EXTRA_EXPLORE_NAME), + sourceUrl = sourceUrl, + exploreUrl = intent.getStringExtra(MainIntent.EXTRA_EXPLORE_URL) + ) + } ?: MainRouteHome + + else -> MainRouteHome + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/main/MainScreen.kt b/app/src/main/java/io/legado/app/ui/main/MainScreen.kt index 8354fba85..555da20fc 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainScreen.kt @@ -113,6 +113,8 @@ fun MainScreen( onNavigateToExploreShow: (title: String?, sourceUrl: String, exploreUrl: String?) -> Unit, onNavigateToRssSort: (sourceUrl: String, sortUrl: String?, key: String?) -> Unit, onNavigateToRssRead: (title: String?, origin: String, link: String?, openUrl: String?) -> Unit, + onNavigateToRssFavorites: () -> Unit, + onNavigateToRuleSub: () -> Unit, onNavigateToReadRecord: () -> Unit, sharedTransitionScope: SharedTransitionScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null, @@ -383,7 +385,9 @@ fun MainScreen( }, onOpenRead = { title, origin, link, openUrl -> onNavigateToRssRead(title, origin, link, openUrl) - } + }, + onOpenFavorites = onNavigateToRssFavorites, + onOpenRuleSub = onNavigateToRuleSub ) MainDestination.My -> MyScreen( onOpenSettings = onOpenSettings, diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt index e99927cf3..2ed1be80c 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt @@ -26,7 +26,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -36,12 +35,12 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow 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.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import io.legado.app.R @@ -49,13 +48,11 @@ import io.legado.app.constant.BookType import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.theme.LegadoTheme -import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.NormalCard import io.legado.app.ui.widget.components.card.TextCard -import io.legado.app.ui.widget.components.cover.CoilBookCover import io.legado.app.ui.widget.components.cover.BookshelfCover +import io.legado.app.ui.widget.components.cover.CoilBookCover import io.legado.app.ui.widget.components.icon.AppIcon -import io.legado.app.ui.widget.components.icon.AppIcons import io.legado.app.ui.widget.components.text.AppText import io.legado.app.utils.splitNotBlank import io.legado.app.utils.toTimeAgo @@ -90,10 +87,11 @@ fun BookshelfItem( onClick: () -> Unit, onLongClick: (() -> Unit)? ) { - val containerColor = if (!isGrid && BookshelfConfig.bookshelfCardColor != 0) { - Color(BookshelfConfig.bookshelfCardColor) - } else if (ThemeConfig.enableDeepPersonalization && ThemeConfig.secondaryThemeColor != 0) { - Color(ThemeConfig.secondaryThemeColor) + val isDark = LegadoTheme.isDark + val bookshelfCardColor = + if (isDark) BookshelfConfig.bookshelfCardColorDark else BookshelfConfig.bookshelfCardColor + val containerColor = if (!isGrid && bookshelfCardColor != 0) { + Color(bookshelfCardColor) } else { LegadoTheme.colorScheme.cardContainer } @@ -189,7 +187,7 @@ fun BookshelfItem( Box( modifier = Modifier .align(Alignment.Top) - .width(if (!isCompact) 80.dp else 56.dp) + .width(if (!isCompact) 84.dp else 56.dp) .padding(end = 8.dp) ) { Box( @@ -209,11 +207,13 @@ fun BookshelfItem( } } Column( - modifier = Modifier.weight(1f).padding(top = 8.dp, bottom = 8.dp, end = 8.dp), + modifier = Modifier + .weight(1f) + .padding(top = 8.dp, bottom = 8.dp, end = 8.dp), verticalArrangement = Arrangement.Center ) { Row( - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.Top ) { AppText( text = title, @@ -222,11 +222,15 @@ fun BookshelfItem( } else { LegadoTheme.typography.titleMediumEmphasized }, - maxLines = 1, + maxLines = BookshelfConfig.bookshelfTitleMaxLines, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f) ) - titleEnd?.invoke() + titleEnd?.let { + Box(modifier = Modifier.padding(top = 4.dp)) { + it.invoke() + } + } } subTitle?.let { AppText( @@ -296,68 +300,82 @@ fun BookGroupCover( modifier = Modifier.fillMaxSize() ) } else { - Column(modifier = Modifier.fillMaxSize()) { - Row(modifier = Modifier.weight(1f)) { - Box( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - .padding(1.dp) - ) { - books.getOrNull(0)?.let { - CoilBookCover( - name = it.name, - author = it.author, - path = it.getDisplayCover(), - modifier = Modifier.fillMaxSize() - ) - } - } - Box( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - .padding(1.dp) - ) { - books.getOrNull(1)?.let { - CoilBookCover( - name = it.name, - author = it.author, - path = it.getDisplayCover(), - modifier = Modifier.fillMaxSize() - ) - } + Box( + modifier = Modifier.run { + if (BookshelfConfig.bookshelfCoverShadow) { + background(LegadoTheme.colorScheme.surface) + } else { + this } } - Row(modifier = Modifier.weight(1f)) { - Box( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - .padding(1.dp) - ) { - books.getOrNull(2)?.let { - CoilBookCover( - name = it.name, - author = it.author, - path = it.getDisplayCover(), - modifier = Modifier.fillMaxSize() - ) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 1.dp), + ) { + Row(modifier = Modifier.weight(1f)) { + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .padding(1.dp) + ) { + books.getOrNull(0)?.let { + CoilBookCover( + name = it.name, + author = it.author, + path = it.getDisplayCover(), + modifier = Modifier.fillMaxSize() + ) + } + } + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .padding(1.dp) + ) { + books.getOrNull(1)?.let { + CoilBookCover( + name = it.name, + author = it.author, + path = it.getDisplayCover(), + modifier = Modifier.fillMaxSize() + ) + } } } - Box( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - .padding(1.dp) - ) { - books.getOrNull(3)?.let { - CoilBookCover( - name = it.name, - author = it.author, - path = it.getDisplayCover(), - modifier = Modifier.fillMaxSize() - ) + Row(modifier = Modifier.weight(1f)) { + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .padding(1.dp) + ) { + books.getOrNull(2)?.let { + CoilBookCover( + name = it.name, + author = it.author, + path = it.getDisplayCover(), + modifier = Modifier.fillMaxSize() + ) + } + } + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .padding(1.dp) + ) { + books.getOrNull(3)?.let { + CoilBookCover( + name = it.name, + author = it.author, + path = it.getDisplayCover(), + modifier = Modifier.fillMaxSize() + ) + } } } } @@ -482,13 +500,16 @@ fun BookGroupItemHorizontalCovers( onBookClick: ((BookShelfItem) -> Unit)? = null ) { Column { + val isDark = LegadoTheme.isDark + val bookshelfCardColor = + if (isDark) BookshelfConfig.bookshelfCardColorDark else BookshelfConfig.bookshelfCardColor NormalCard( modifier = modifier .fillMaxWidth() .padding(all = 4.dp), cornerRadius = 12.dp, - containerColor = if (BookshelfConfig.bookshelfCardColor != 0) { - Color(BookshelfConfig.bookshelfCardColor) + containerColor = if (bookshelfCardColor != 0) { + Color(bookshelfCardColor) } else { LegadoTheme.colorScheme.cardContainer }, @@ -634,7 +655,9 @@ fun BookItem( path = book.getDisplayCover(), isUpdating = isUpdating, modifier = modifier, - coverModifier = Modifier.fillMaxWidth().aspectRatio(5f / 7f), + coverModifier = Modifier + .fillMaxWidth() + .aspectRatio(5f / 7f), sourceOrigin = book.origin, badgeText = if (layoutMode != 0) unreadText else null, showBadgeDot = BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && book.isNew, diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt index fc8f5bf3d..4091aa5e8 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt @@ -3,11 +3,16 @@ package io.legado.app.ui.main.bookshelf import android.content.res.Configuration import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme @@ -17,6 +22,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringArrayResource @@ -27,8 +34,8 @@ import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig import io.legado.app.ui.config.themeConfig.LabelColorManageSheet import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.theme.LegadoTheme -import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.dialog.ColorPickerSheet +import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.settingItem.CompactClickableSettingItem import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem @@ -45,15 +52,13 @@ fun BookshelfConfigSheet( val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE var showLabelColorManage by remember { mutableStateOf(false) } var showColorPicker by remember { mutableStateOf(false) } + var showColorPickerDark by remember { mutableStateOf(false) } AppModalBottomSheet( title = stringResource(R.string.bookshelf_layout), show = show, onDismissRequest = onDismissRequest ) { - GlassCard() { - - } Column( modifier = Modifier .fillMaxWidth() @@ -99,21 +104,12 @@ fun BookshelfConfigSheet( if (isLandscape) BookshelfConfig.bookshelfFolderLayoutModeLandscape else BookshelfConfig.bookshelfFolderLayoutModePortrait - CompactDropdownSettingItem( - title = stringResource(R.string.layout_mode), - description = stringResource(if (isLandscape) R.string.screen_landscape else R.string.screen_portrait), - selectedValue = layoutMode.toString(), - displayEntries = arrayOf(stringResource(R.string.layout_mode_list),stringResource(R.string.layout_mode_grid)), - entryValues = arrayOf("0", "1"), - onValueChange = { - if (isLandscape) BookshelfConfig.bookshelfLayoutModeLandscape = it.toInt() - else BookshelfConfig.bookshelfLayoutModePortrait = it.toInt() - } - ) - // Folder Layout Mode AnimatedVisibility(visible = BookshelfConfig.bookGroupStyle == 2) { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + + PillDivider() + CompactDropdownSettingItem( title = "文件夹布局模式", description = stringResource(if (isLandscape) R.string.screen_landscape else R.string.screen_portrait), @@ -157,33 +153,54 @@ fun BookshelfConfigSheet( } ) } + + AnimatedVisibility( + visible = BookshelfConfig.bookGroupStyle == 2 && folderLayoutMode == 0 + ) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + CompactDropdownSettingItem( + title = "文件夹列表样式", + selectedValue = BookshelfConfig.bookshelfGroupListStyle.toString(), + displayEntries = arrayOf("默认", "紧凑", "横排封面"), + entryValues = arrayOf("0", "1", "2"), + onValueChange = { + BookshelfConfig.bookshelfGroupListStyle = it.toInt() + } + ) + AnimatedVisibility(visible = BookshelfConfig.bookshelfGroupListStyle == 2) { + CompactSliderSettingItem( + title = "横排封面数量", + value = BookshelfConfig.bookshelfGroupCoverCount.toFloat(), + valueRange = 1f..10f, + steps = 9, + onValueChange = { + BookshelfConfig.bookshelfGroupCoverCount = it.toInt() + } + ) + } + } + } + + PillDivider() } } - AnimatedVisibility( - visible = BookshelfConfig.bookGroupStyle == 2 && folderLayoutMode == 0 - ) { - Column( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - CompactDropdownSettingItem( - title = "文件夹列表样式", - selectedValue = BookshelfConfig.bookshelfGroupListStyle.toString(), - displayEntries = arrayOf("默认", "紧凑", "横排封面"), - entryValues = arrayOf("0", "1", "2"), - onValueChange = { BookshelfConfig.bookshelfGroupListStyle = it.toInt() } - ) - AnimatedVisibility(visible = BookshelfConfig.bookshelfGroupListStyle == 2) { - CompactSliderSettingItem( - title = "横排封面数量", - value = BookshelfConfig.bookshelfGroupCoverCount.toFloat(), - valueRange = 1f..10f, - steps = 9, - onValueChange = { BookshelfConfig.bookshelfGroupCoverCount = it.toInt() } - ) - } + CompactDropdownSettingItem( + title = stringResource(R.string.layout_mode), + description = stringResource(if (isLandscape) R.string.screen_landscape else R.string.screen_portrait), + selectedValue = layoutMode.toString(), + displayEntries = arrayOf( + stringResource(R.string.layout_mode_list), + stringResource(R.string.layout_mode_grid) + ), + entryValues = arrayOf("0", "1"), + onValueChange = { + if (isLandscape) BookshelfConfig.bookshelfLayoutModeLandscape = it.toInt() + else BookshelfConfig.bookshelfLayoutModePortrait = it.toInt() } - } + ) AnimatedVisibility( visible = layoutMode == 1 @@ -243,103 +260,135 @@ fun BookshelfConfigSheet( ) CompactClickableSettingItem( - title = "卡片背景颜色", + title = "日间卡片背景颜色", color = LegadoTheme.colorScheme.surface, - onClick = { showColorPicker = true } + onClick = { showColorPicker = true }, + trailingContent = { + if (BookshelfConfig.bookshelfCardColor != 0) { + Box( + modifier = Modifier + .size(20.dp) + .clip(CircleShape) + .background(Color(BookshelfConfig.bookshelfCardColor)) + .border( + 1.dp, + MaterialTheme.colorScheme.outlineVariant, + CircleShape + ) + ) + } + } ) - val isHorizontalCovers = BookshelfConfig.bookGroupStyle == 2 && BookshelfConfig.bookshelfGroupListStyle == 2 + CompactClickableSettingItem( + title = "夜间卡片背景颜色", + color = LegadoTheme.colorScheme.surface, + onClick = { showColorPickerDark = true }, + trailingContent = { + if (BookshelfConfig.bookshelfCardColorDark != 0) { + Box( + modifier = Modifier + .size(20.dp) + .clip(CircleShape) + .background(Color(BookshelfConfig.bookshelfCardColorDark)) + .border( + 1.dp, + MaterialTheme.colorScheme.outlineVariant, + CircleShape + ) + ) + } + } + ) - AnimatedVisibility(visible = !isHorizontalCovers) { + if (BookshelfConfig.bookGroupStyle != 2) { + CompactSwitchSettingItem( + title = stringResource(R.string.compact_mode), + checked = BookshelfConfig.bookshelfLayoutCompact, + color = LegadoTheme.colorScheme.surface, + onCheckedChange = { BookshelfConfig.bookshelfLayoutCompact = it } + ) + } + + val listColCount = + if (isLandscape) BookshelfConfig.bookshelfLayoutListLandscape else BookshelfConfig.bookshelfLayoutListPortrait + CompactSliderSettingItem( + title = stringResource(R.string.number_rows_columns), + value = listColCount.toFloat(), + valueRange = 1f..5f, + steps = 4, + onValueChange = { + if (isLandscape) BookshelfConfig.bookshelfLayoutListLandscape = + it.toInt() + else BookshelfConfig.bookshelfLayoutListPortrait = it.toInt() + } + ) + + CompactSwitchSettingItem( + title = "显示更多信息", + checked = BookshelfConfig.showBookIntro, + color = LegadoTheme.colorScheme.surface, + onCheckedChange = { BookshelfConfig.showBookIntro = it } + ) + + AnimatedVisibility(visible = BookshelfConfig.showBookIntro) { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { - if (BookshelfConfig.bookGroupStyle != 2) { - CompactSwitchSettingItem( - title = stringResource(R.string.compact_mode), - checked = BookshelfConfig.bookshelfLayoutCompact, - color = LegadoTheme.colorScheme.surface, - onCheckedChange = { BookshelfConfig.bookshelfLayoutCompact = it } - ) - } - - val listColCount = - if (isLandscape) BookshelfConfig.bookshelfLayoutListLandscape else BookshelfConfig.bookshelfLayoutListPortrait - CompactSliderSettingItem( - title = stringResource(R.string.number_rows_columns), - value = listColCount.toFloat(), - valueRange = 1f..5f, - steps = 4, - onValueChange = { - if (isLandscape) BookshelfConfig.bookshelfLayoutListLandscape = - it.toInt() - else BookshelfConfig.bookshelfLayoutListPortrait = it.toInt() + CompactSwitchSettingItem( + title = "显示最新章节", + checked = BookshelfConfig.bookshelfShowLatestChapter, + color = LegadoTheme.colorScheme.surface, + onCheckedChange = { + BookshelfConfig.bookshelfShowLatestChapter = it } ) CompactSwitchSettingItem( - title = "显示更多信息", - checked = BookshelfConfig.showBookIntro, + title = "显示简介", + checked = BookshelfConfig.bookshelfShowIntro, color = LegadoTheme.colorScheme.surface, - onCheckedChange = { BookshelfConfig.showBookIntro = it } + onCheckedChange = { BookshelfConfig.bookshelfShowIntro = it } ) - - AnimatedVisibility(visible = BookshelfConfig.showBookIntro) { + AnimatedVisibility( + visible = BookshelfConfig.bookshelfShowIntro + ) { + CompactSliderSettingItem( + title = "简介行数", + description = if (BookshelfConfig.bookshelfIntroMaxLines == 0) "显示全部简介" else "显示 ${BookshelfConfig.bookshelfIntroMaxLines} 行简介", + value = BookshelfConfig.bookshelfIntroMaxLines.toFloat(), + valueRange = 0f..10f, + steps = 10, + onValueChange = { + BookshelfConfig.bookshelfIntroMaxLines = it.toInt() + } + ) + } + CompactSwitchSettingItem( + title = "显示标签", + checked = BookshelfConfig.bookshelfShowTag, + color = LegadoTheme.colorScheme.surface, + onCheckedChange = { BookshelfConfig.bookshelfShowTag = it } + ) + AnimatedVisibility( + visible = BookshelfConfig.bookshelfShowTag + ) { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { CompactSwitchSettingItem( - title = "显示最新章节", - checked = BookshelfConfig.bookshelfShowLatestChapter, + title = "自定义标签颜色", + checked = ThemeConfig.enableCustomTagColors, color = LegadoTheme.colorScheme.surface, - onCheckedChange = { BookshelfConfig.bookshelfShowLatestChapter = it } + onCheckedChange = { ThemeConfig.enableCustomTagColors = it } ) - - CompactSwitchSettingItem( - title = "显示简介", - checked = BookshelfConfig.bookshelfShowIntro, - color = LegadoTheme.colorScheme.surface, - onCheckedChange = { BookshelfConfig.bookshelfShowIntro = it } - ) - AnimatedVisibility( - visible = BookshelfConfig.bookshelfShowIntro - ) { - CompactSliderSettingItem( - title = "简介行数", - description = if (BookshelfConfig.bookshelfIntroMaxLines == 0) "显示全部简介" else "显示 ${BookshelfConfig.bookshelfIntroMaxLines} 行简介", - value = BookshelfConfig.bookshelfIntroMaxLines.toFloat(), - valueRange = 0f..10f, - steps = 10, - onValueChange = { BookshelfConfig.bookshelfIntroMaxLines = it.toInt() } + AnimatedVisibility(visible = ThemeConfig.enableCustomTagColors) { + CompactClickableSettingItem( + title = "管理标签颜色", + color = LegadoTheme.colorScheme.surface, + onClick = { showLabelColorManage = true } ) } - CompactSwitchSettingItem( - title = "显示标签", - checked = BookshelfConfig.bookshelfShowTag, - color = LegadoTheme.colorScheme.surface, - onCheckedChange = { BookshelfConfig.bookshelfShowTag = it } - ) - AnimatedVisibility( - visible = BookshelfConfig.bookshelfShowTag - ) { - Column( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - CompactSwitchSettingItem( - title = "自定义标签颜色", - checked = ThemeConfig.enableCustomTagColors, - color = LegadoTheme.colorScheme.surface, - onCheckedChange = { ThemeConfig.enableCustomTagColors = it } - ) - AnimatedVisibility(visible = ThemeConfig.enableCustomTagColors) { - CompactClickableSettingItem( - title = "管理标签颜色", - color = LegadoTheme.colorScheme.surface, - onClick = { showLabelColorManage = true } - ) - } - } - } } } } @@ -347,6 +396,8 @@ fun BookshelfConfigSheet( } } + + CompactSliderSettingItem( title = stringResource(R.string.max_title_lines), value = BookshelfConfig.bookshelfTitleMaxLines.toFloat(), @@ -448,5 +499,12 @@ fun BookshelfConfigSheet( onDismissRequest = { showColorPicker = false }, onColorSelected = { BookshelfConfig.bookshelfCardColor = it } ) + + ColorPickerSheet( + show = showColorPickerDark, + initialColor = if (BookshelfConfig.bookshelfCardColorDark != 0) BookshelfConfig.bookshelfCardColorDark else LegadoTheme.colorScheme.surfaceVariant.toArgb(), + onDismissRequest = { showColorPickerDark = false }, + onColorSelected = { BookshelfConfig.bookshelfCardColorDark = it } + ) } } diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt index b2499e477..d7eda8276 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt @@ -778,7 +778,7 @@ fun BookshelfScreen( ), contentPadding = adaptiveContentPaddingBookshelf( top = paddingValues.calculateTopPadding(), - bottom = if (ThemeConfig.useFloatingBottomBar) 120.dp else 8.dp, + bottom = if (ThemeConfig.useFloatingBottomBar || ThemeConfig.enableBlur) 120.dp else 8.dp, horizontal = if (isGridMode) 8.dp else 4.dp ), verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp), @@ -1342,7 +1342,7 @@ fun BookshelfPage( ), contentPadding = adaptiveContentPaddingBookshelf( top = paddingValues.calculateTopPadding(), - bottom = if (ThemeConfig.useFloatingBottomBar) 120.dp else 8.dp, + bottom = if (ThemeConfig.useFloatingBottomBar || ThemeConfig.enableBlur) 120.dp else 8.dp, horizontal = if (isGridMode) 8.dp else 4.dp ), verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp), diff --git a/app/src/main/java/io/legado/app/ui/main/rss/RssScreen.kt b/app/src/main/java/io/legado/app/ui/main/rss/RssScreen.kt index de843d4eb..4cf54cd85 100644 --- a/app/src/main/java/io/legado/app/ui/main/rss/RssScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/rss/RssScreen.kt @@ -45,10 +45,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.legado.app.R import io.legado.app.data.entities.RssSource import io.legado.app.ui.login.SourceLoginActivity -import io.legado.app.ui.rss.favorites.RssFavoritesActivity import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.ui.rss.source.manage.RssSourceActivity -import io.legado.app.ui.rss.subscription.RuleSubActivity import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.adaptiveContentPadding import io.legado.app.ui.widget.components.SourceIcon @@ -71,7 +69,9 @@ import org.koin.androidx.compose.koinViewModel fun RssScreen( viewModel: RssViewModel = koinViewModel(), onOpenSort: (sourceUrl: String, sortUrl: String?, key: String?) -> Unit, - onOpenRead: (title: String?, origin: String, link: String?, openUrl: String?) -> Unit + onOpenRead: (title: String?, origin: String, link: String?, openUrl: String?) -> Unit, + onOpenRuleSub: () -> Unit, + onOpenFavorites: () -> Unit, ) { val context = LocalContext.current val uiState by viewModel.uiState.collectAsStateWithLifecycle() @@ -82,6 +82,8 @@ fun RssScreen( val currentContext by rememberUpdatedState(context) val currentOnOpenSort by rememberUpdatedState(onOpenSort) val currentOnOpenRead by rememberUpdatedState(onOpenRead) + val currentOnOpenRuleSub by rememberUpdatedState(onOpenRuleSub) + val currentOnOpenFavorites by rememberUpdatedState(onOpenFavorites) LaunchedEffect(viewModel) { viewModel.effects.collectLatest { effect -> @@ -112,11 +114,11 @@ fun RssScreen( } RssEffect.OpenRuleSub -> { - currentContext.startActivity() + currentOnOpenRuleSub() } RssEffect.OpenFavorites -> { - currentContext.startActivity() + currentOnOpenFavorites() } RssEffect.OpenSourceManage -> { diff --git a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt deleted file mode 100644 index ef25f1081..000000000 --- a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt +++ /dev/null @@ -1,52 +0,0 @@ -package io.legado.app.ui.rss.favorites - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import io.legado.app.base.BaseComposeActivity -import io.legado.app.ui.rss.read.RssReadRouteScreen - -/** - * 收藏夹 - */ -class RssFavoritesActivity : BaseComposeActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - } - - @Composable - override fun Content() { - var readArgs by remember { - mutableStateOf(null) - } - - val currentReadArgs = readArgs - if (currentReadArgs == null) { - RssFavoritesScreen( - onBackClick = { finish() }, - onOpenRead = { title, origin, link, openUrl -> - readArgs = ReadArgs(title, origin, link, openUrl) - } - ) - } else { - RssReadRouteScreen( - title = currentReadArgs.title, - origin = currentReadArgs.origin, - link = currentReadArgs.link, - openUrl = currentReadArgs.openUrl, - onBackClick = { readArgs = null } - ) - } - } - - private data class ReadArgs( - val title: String?, - val origin: String, - val link: String?, - val openUrl: String? - ) -} diff --git a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt index 8396d9a0c..750840969 100644 --- a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt +++ b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt @@ -28,7 +28,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import io.legado.app.R import io.legado.app.data.entities.RssStar import io.legado.app.ui.theme.adaptiveContentPadding @@ -41,13 +40,14 @@ import io.legado.app.ui.widget.components.dialog.TextListInputDialog import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.rules.RuleListScaffold +import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun RssFavoritesScreen( onBackClick: () -> Unit, onOpenRead: (title: String?, origin: String, link: String?, openUrl: String?) -> Unit, - viewModel: RssFavoritesViewModel = viewModel() + viewModel: RssFavoritesViewModel = koinViewModel() ) { val state by viewModel.state.collectAsState() val groups by viewModel.groups.collectAsState() diff --git a/app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubActivity.kt b/app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubActivity.kt deleted file mode 100644 index 500b0edb9..000000000 --- a/app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubActivity.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.legado.app.ui.rss.subscription - -import android.os.Bundle -import androidx.compose.runtime.Composable -import io.legado.app.base.BaseComposeActivity - -/** - * 规则订阅界面 - */ -class RuleSubActivity : BaseComposeActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - } - - @Composable - override fun Content() { - RuleSubScreen( - onBackClick = { finish() } - ) - } - -} diff --git a/app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubScreen.kt b/app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubScreen.kt index a23a9b89b..2ce8bf638 100644 --- a/app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubScreen.kt +++ b/app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubScreen.kt @@ -18,7 +18,6 @@ import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import io.legado.app.ui.widget.components.AppFloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.SnackbarHostState @@ -37,7 +36,6 @@ import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.net.toUri -import androidx.lifecycle.viewmodel.compose.viewModel import io.legado.app.R import io.legado.app.data.entities.RuleSub import io.legado.app.data.entities.RuleSubType @@ -46,6 +44,7 @@ import io.legado.app.ui.association.ImportReplaceRuleDialog import io.legado.app.ui.association.ImportRssSourceDialog import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.adaptiveContentPadding +import io.legado.app.ui.widget.components.AppFloatingActionButton import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.alert.AppAlertDialog @@ -57,12 +56,13 @@ import io.legado.app.ui.widget.components.rules.RuleListScaffold import io.legado.app.ui.widget.components.text.AppText import io.legado.app.utils.showDialogFragment import io.legado.app.utils.toastOnUi +import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun RuleSubScreen( onBackClick: () -> Unit, - viewModel: RuleSubViewModel = viewModel() + viewModel: RuleSubViewModel = koinViewModel() ) { val context = LocalContext.current val state by viewModel.state.collectAsState() diff --git a/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt b/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt index 3b4bfe99e..c6a560eb1 100644 --- a/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt +++ b/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt @@ -55,7 +55,7 @@ fun adaptiveContentPadding( bottom: Dp ): PaddingValues { val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp else 16.dp - val adjustedTop = if (ThemeResolver.isMiuixEngine(composeEngine)) top + 8.dp else top + val adjustedTop = if (ThemeResolver.isMiuixEngine(composeEngine)) top + 12.dp else top + 16.dp return PaddingValues( top = adjustedTop, bottom = bottom, @@ -70,7 +70,7 @@ fun adaptiveContentPadding( bottom: Dp, horizontal: Dp ): PaddingValues { - val adjustedTop = if (ThemeResolver.isMiuixEngine(composeEngine)) top + 6.dp else top + 4.dp + val adjustedTop = if (ThemeResolver.isMiuixEngine(composeEngine)) top + 12.dp else top + 16.dp return PaddingValues( top = adjustedTop, bottom = bottom, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/book/SearchBookItem.kt b/app/src/main/java/io/legado/app/ui/widget/components/book/SearchBookItem.kt index c9804b9aa..3b873aab5 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/book/SearchBookItem.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/book/SearchBookItem.kt @@ -5,16 +5,16 @@ import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -23,37 +23,18 @@ import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Shuffle import androidx.compose.material3.Surface import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import io.legado.app.data.entities.SearchBook import io.legado.app.domain.model.BookShelfState import io.legado.app.ui.theme.LegadoTheme +import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.cover.CoilBookCover import io.legado.app.ui.widget.components.text.AppText -import kotlinx.coroutines.flow.Flow - -@Composable -fun SearchBookListItem( - book: SearchBook, - shelfState: Flow, - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - val currentShelfState by shelfState.collectAsState(initial = BookShelfState.NOT_IN_SHELF) - SearchBookListItem( - book = book, - shelfState = currentShelfState, - onClick = onClick, - modifier = modifier, - ) -} @OptIn(ExperimentalSharedTransitionApi::class) @Composable @@ -72,17 +53,39 @@ fun SearchBookListItem( .clickable(onClick = onClick) .padding(horizontal = 16.dp, vertical = 8.dp) ) { - CoilBookCover( - name = book.name, - author = book.author, - path = book.coverUrl, - modifier = Modifier.width(72.dp).aspectRatio(5f / 7f), - sourceOrigin = book.origin, - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, - sharedCoverKey = sharedCoverKey, - showLoadingPlaceholder = sharedCoverKey == null - ) + Box(modifier = Modifier + .width(72.dp) + .aspectRatio(5f / 7f)) { + CoilBookCover( + name = book.name, + author = book.author, + path = book.coverUrl, + modifier = Modifier.fillMaxSize(), + sourceOrigin = book.origin, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + sharedCoverKey = sharedCoverKey, + showLoadingPlaceholder = sharedCoverKey == null + ) + + val shelfIcon = when (shelfState) { + BookShelfState.IN_SHELF -> Icons.Default.Check + BookShelfState.SAME_NAME_AUTHOR -> Icons.Default.Shuffle + else -> null + } + + if (shelfIcon != null) { + TextCard( + icon = shelfIcon, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(2.dp), + cornerRadius = 4.dp, + horizontalPadding = 2.dp, + verticalPadding = 2.dp + ) + } + } Spacer(modifier = Modifier.width(8.dp)) @@ -151,22 +154,6 @@ fun SearchBookListItem( } } -@Composable -fun SearchBookGridItem( - book: SearchBook, - shelfState: Flow, - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - val currentShelfState by shelfState.collectAsState(initial = BookShelfState.NOT_IN_SHELF) - SearchBookGridItem( - book = book, - shelfState = currentShelfState, - onClick = onClick, - modifier = modifier, - ) -} - @OptIn(ExperimentalSharedTransitionApi::class) @Composable fun SearchBookGridItem( @@ -184,19 +171,41 @@ fun SearchBookGridItem( .clickable(onClick = onClick) .padding(4.dp) ) { - CoilBookCover( - name = book.name, - author = book.author, - path = book.coverUrl, + Box( modifier = Modifier .fillMaxWidth() - .aspectRatio(5f / 7f), - sourceOrigin = book.origin, - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, - sharedCoverKey = sharedCoverKey, - showLoadingPlaceholder = sharedCoverKey == null - ) + .aspectRatio(5f / 7f) + ) { + CoilBookCover( + name = book.name, + author = book.author, + path = book.coverUrl, + modifier = Modifier.fillMaxSize(), + sourceOrigin = book.origin, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + sharedCoverKey = sharedCoverKey, + showLoadingPlaceholder = sharedCoverKey == null + ) + + val shelfIcon = when (shelfState) { + BookShelfState.IN_SHELF -> Icons.Default.Check + BookShelfState.SAME_NAME_AUTHOR -> Icons.Default.Shuffle + else -> null + } + + if (shelfIcon != null) { + TextCard( + icon = shelfIcon, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(2.dp), + cornerRadius = 4.dp, + horizontalPadding = 2.dp, + verticalPadding = 2.dp + ) + } + } Spacer(modifier = Modifier.height(4.dp)) diff --git a/app/src/main/java/io/legado/app/ui/widget/components/divider/PillDivider.kt b/app/src/main/java/io/legado/app/ui/widget/components/divider/PillDivider.kt index e1c66ed41..508b60fd7 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/divider/PillDivider.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/divider/PillDivider.kt @@ -27,12 +27,18 @@ fun PillDivider( val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine) if (isMiuix) { Box( - modifier = Modifier + modifier = modifier .fillMaxWidth() - .padding(vertical = 2.dp) - .height(4.dp) - .background(MiuixTheme.colorScheme.dividerLine) - ) + .padding(vertical = 4.dp), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .fillMaxWidth(0.8f) + .height(thickness) + .background(MiuixTheme.colorScheme.dividerLine) + ) + } } else { Box( modifier = modifier diff --git a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/CompactSettingItems.kt b/app/src/main/java/io/legado/app/ui/widget/components/settingItem/CompactSettingItems.kt index 6db85f5eb..f8a942cb3 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/CompactSettingItems.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/settingItem/CompactSettingItems.kt @@ -48,7 +48,7 @@ fun CompactDropdownSettingItem( entryValues: Array, description: String? = null, imageVector: ImageVector? = null, - color: Color? = LegadoTheme.colorScheme.surface, + color: Color? = LegadoTheme.colorScheme.onSheetContent, shape: Shape = MaterialTheme.shapes.small, onValueChange: (String) -> Unit ) { @@ -126,7 +126,7 @@ fun CompactSliderSettingItem( steps: Int = 0, description: String? = null, imageVector: ImageVector? = null, - color: Color? = MaterialTheme.colorScheme.surface, + color: Color? = LegadoTheme.colorScheme.onSheetContent, shape: Shape = MaterialTheme.shapes.small, onValueChange: (Float) -> Unit ) { @@ -222,7 +222,7 @@ fun CompactSwitchSettingItem( checked: Boolean, description: String? = null, imageVector: ImageVector? = null, - color: Color? = MaterialTheme.colorScheme.surface, + color: Color? = LegadoTheme.colorScheme.onSheetContent, shape: Shape = MaterialTheme.shapes.small, enabled: Boolean = true, onCheckedChange: (Boolean) -> Unit @@ -260,7 +260,7 @@ fun CompactClickableSettingItem( title: String, description: String? = null, imageVector: ImageVector? = null, - color: Color? = MaterialTheme.colorScheme.surface, + color: Color? = LegadoTheme.colorScheme.onSheetContent, shape: Shape = MaterialTheme.shapes.small, trailingContent: (@Composable () -> Unit)? = null, onClick: () -> Unit