界面优化

This commit is contained in:
HapeLee
2026-05-15 21:20:49 +08:00
parent f411b7136e
commit e64e50bf38
23 changed files with 1436 additions and 1274 deletions
@@ -271,6 +271,7 @@ object PreferKey {
const val bookshelfCoverShadow = "bookshelfCoverShadow"
const val bookshelfSearchActionDirectToSearch = "bookshelfSearchActionDirectToSearch"
const val bookshelfCardColor = "bookshelfCardColor"
const val bookshelfCardColorDark = "bookshelfCardColorDark"
const val bookshelfGroupListStyle = "bookshelfGroupListStyle"
const val bookshelfGroupCoverCount = "bookshelfGroupCoverCount"
@@ -723,8 +723,8 @@ interface BookDao {
type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind
FROM books
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 flowBookShelfPreview(): Flow<List<BookShelfItem>>
@@ -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<List<BookShelfItem>>
@@ -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<List<BookShelfItem>>
@@ -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<List<BookShelfItem>>
@@ -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<List<BookShelfItem>>
@@ -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<List<BookShelfItem>>
@@ -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<List<BookShelfItem>>
@@ -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<List<BookShelfItem>>
@@ -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<List<BookShelfItem>>
@@ -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<List<BookShelfItem>>
@@ -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<List<BookShelfItem>>
@@ -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<List<BookShelfItem>>
@@ -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<List<BookShelfItem>>
@@ -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)
@@ -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<Int> get() = _bookshelfCardColor.state
/**
* 书架卡片背景颜色 (夜间)
*/
private val _bookshelfCardColorDark = prefStateDelegate(PreferKey.bookshelfCardColorDark, 0)
var bookshelfCardColorDark by _bookshelfCardColorDark
val bookshelfCardColorDarkState: State<Int> get() = _bookshelfCardColorDark.state
/**
* 文件夹在列表模式下的样式: 0: 默认, 2: 横排封面
*/
File diff suppressed because it is too large Load Diff
@@ -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
}
}
}
@@ -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<NavKey>,
useRail: Boolean,
sharedTransitionScope: SharedTransitionScope,
onNavigateToRoute: (NavKey) -> Unit,
onNavigateBack: () -> Unit,
onRegisterVariableSetter: (((String, String?) -> Unit)?) -> Unit
) = entryProvider {
entry<MainRouteHome> {
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<MainRouteSettings> {
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<MainRouteSettingsOther> {
OtherConfigScreen(onBackClick = { onNavigateBack() })
}
entry<MainRouteSettingsRead> {
ReadConfigScreen(onBackClick = { onNavigateBack() })
}
entry<MainRouteSettingsCover> {
CoverConfigScreen(onBackClick = { onNavigateBack() })
}
entry<MainRouteSettingsTheme> {
ThemeConfigScreen(
onBackClick = { onNavigateBack() },
onNavigateToCustomTheme = { backStack.add(MainRouteSettingsCustomTheme) },
onNavigateToThemeManage = { backStack.add(MainRouteSettingsThemeManage) }
)
}
entry<MainRouteSettingsBackup> {
BackupConfigScreen(onBackClick = { onNavigateBack() })
}
entry<MainRouteSettingsDownloadCache> {
DownloadCacheConfigScreen(onBackClick = { onNavigateBack() })
}
entry<MainRouteSettingsCustomTheme> {
CustomThemeScreen(
onBackClick = { onNavigateBack() }
)
}
entry<MainRouteSettingsThemeManage> {
ThemeManageScreen(onBackClick = { onNavigateBack() })
}
entry<MainRouteImportLocal> {
ImportBookScreen(
onBackClick = { onNavigateBack() }
)
}
entry<MainRouteImportRemote> {
RemoteBookScreen(
onBackClick = { onNavigateBack() }
)
}
entry<MainRouteCache> { route ->
BookshelfManageRouteScreen(
groupId = route.groupId,
onBackClick = { onNavigateBack() },
onOpenBookInfo = { name, author, bookUrl ->
onNavigateToRoute(
MainRouteBookInfo(
name = name,
author = author,
bookUrl = bookUrl
)
)
}
)
}
entry<MainRouteBookCacheManage> {
BookCacheManageRouteScreen(
onBackClick = { onNavigateBack() }
)
}
entry<MainRouteSearch> { route ->
val searchViewModel = koinViewModel<SearchViewModel>()
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<BookSourceActivity>()
},
sharedTransitionScope = sharedTransitionScope,
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
)
}
entry<MainRouteRssSort> { 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<MainRouteRssRead> { route ->
RssReadRouteScreen(
title = route.title,
origin = route.origin,
link = route.link,
openUrl = route.openUrl,
onBackClick = { onNavigateBack() }
)
}
entry<MainRouteRssFavorites> {
RssFavoritesScreen(
onBackClick = { onNavigateBack() },
onOpenRead = { title, origin, link, openUrl ->
onNavigateToRoute(
MainRouteRssRead(
title = title,
origin = origin,
link = link,
openUrl = openUrl
)
)
}
)
}
entry<MainRouteRuleSub> {
RuleSubScreen(
onBackClick = { onNavigateBack() }
)
}
entry<MainRouteReadRecord> {
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<MainRouteReadRecordOverview> {
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<MainRouteBookInfo>(
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<BookInfoViewModel>()
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<MainRouteExploreShow> { 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,
)
}
}
@@ -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"
}
@@ -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<NavKey>, 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<NavKey>) {
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
}
}
}
@@ -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,
@@ -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,
@@ -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 }
)
}
}
@@ -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),
@@ -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<RuleSubActivity>()
currentOnOpenRuleSub()
}
RssEffect.OpenFavorites -> {
currentContext.startActivity<RssFavoritesActivity>()
currentOnOpenFavorites()
}
RssEffect.OpenSourceManage -> {
@@ -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<ReadArgs?>(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?
)
}
@@ -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()
@@ -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() }
)
}
}
@@ -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()
@@ -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,
@@ -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<BookShelfState>,
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<BookShelfState>,
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))
@@ -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
@@ -48,7 +48,7 @@ fun CompactDropdownSettingItem(
entryValues: Array<String>,
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