diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 81c835ab0..4e6bf0320 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -50,7 +50,12 @@
"Bash(npx skills *)",
"Bash(npx *)",
"PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 20)",
- "Bash(cmd /c \"gradlew.bat :app:compileAppDebugKotlin\")"
+ "Bash(cmd /c \"gradlew.bat :app:compileAppDebugKotlin\")",
+ "Bash(cmd /c \"gradlew.bat :app:compileAppDebugKotlin 2>&1\")",
+ "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 40)",
+ "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 100)",
+ "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 50)",
+ "Bash(python3 *)"
]
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 677a36879..fb2dfc5c7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -291,12 +291,6 @@
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true"
android:theme="@style/AppTheme.Transparent" />
-
-
by lazy {
- ArrayList(getConfigs() ?: emptyList())
- }
-
- val configList: List get() = _configList
-
- fun save() {
- val json = THEME_GSON.toJson(_configList)
- FileUtils.delete(configFilePath)
- FileUtils.createFileIfNotExist(configFilePath).writeText(json)
- }
-
- fun toJson(config: Config): String {
- return THEME_GSON.toJson(config)
- }
-
- fun toJson(): String {
- return THEME_GSON.toJson(_configList)
- }
-
- fun fromJson(json: String): Boolean {
- return kotlin.runCatching {
- val configs = THEME_GSON.fromJson(json, Array::class.java)
- _configList.clear()
- _configList.addAll(configs)
- save()
- true
- }.getOrDefault(false)
- }
-
- fun delConfig(index: Int) {
- if (index in _configList.indices) {
- _configList.removeAt(index)
- save()
- }
- }
-
- fun addConfig(json: String): Boolean {
- return kotlin.runCatching {
- val config = THEME_GSON.fromJson(json, Config::class.java)
- addConfig(config)
- true
- }.getOrDefault(false)
- }
-
- fun addConfig(newConfig: Config) {
- _configList.forEachIndexed { index, config ->
- if (newConfig.themeName == config.themeName) {
- _configList[index] = newConfig
- save()
- return
- }
- }
- _configList.add(newConfig)
- save()
- }
-
- private fun getConfigs(): List? {
- val configFile = File(configFilePath)
- if (configFile.exists()) {
- kotlin.runCatching {
- val json = configFile.readText()
- return THEME_GSON.fromJson(json, Array::class.java).toList()
- }
- }
- return null
- }
-
- fun applyConfig(config: Config) {
- config.appearance.applyToThemeConfig()
- }
-
- fun savePersonalizationTheme(name: String): Config {
- val config = Config(
- themeName = name,
- appearance = ThemeAppearanceConfig.fromCurrent()
- )
- addConfig(config)
- return config
- }
-
- data class Config(
- var themeName: String = "",
- var appearance: ThemeAppearanceConfig = ThemeAppearanceConfig()
- ) {
- val themeColor: Int get() = appearance.themeColor
- }
-}
-
-class ConfigSerializer : JsonSerializer,
- JsonDeserializer {
-
- override fun serialize(
- src: PersonalizationThemeConfig.Config,
- typeOfSrc: Type,
- context: JsonSerializationContext
- ): JsonElement {
- val obj = JsonObject()
- obj.addProperty("themeName", src.themeName)
- ThemeAppearanceJson.writeTo(obj, src.appearance)
- return obj
- }
-
- override fun deserialize(
- json: JsonElement,
- typeOfT: Type,
- context: JsonDeserializationContext
- ): PersonalizationThemeConfig.Config {
- val obj = json.asJsonObject
- return PersonalizationThemeConfig.Config(
- themeName = obj.getString("themeName", "") ?: "",
- appearance = ThemeAppearanceJson.readFrom(obj)
- )
- }
-
-}
diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt
index 3bd89ec55..04c7a0d5b 100644
--- a/app/src/main/java/io/legado/app/help/storage/Restore.kt
+++ b/app/src/main/java/io/legado/app/help/storage/Restore.kt
@@ -34,7 +34,6 @@ import io.legado.app.help.book.isLocal
import io.legado.app.help.book.upType
import io.legado.app.help.config.LocalConfig
import io.legado.app.help.config.OldThemeConfig
-import io.legado.app.help.config.PersonalizationThemeConfig
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.model.BookCover
import io.legado.app.model.localBook.LocalBook
@@ -284,16 +283,6 @@ object Restore {
}?.onFailure {
AppLog.put("恢复封面规则出错\n${it.localizedMessage}", it)
}
- if (!BackupConfig.ignorePersonalizationThemeConfig) {
- File(path, PersonalizationThemeConfig.configFileName).takeIf {
- it.exists()
- }?.runCatching {
- val json = readText()
- PersonalizationThemeConfig.fromJson(json)
- }?.onFailure {
- AppLog.put("恢复个性化主题出错\n${it.localizedMessage}", it)
- }
- }
if (!BackupConfig.ignoreReadConfig) {
//恢复阅读界面配置
File(path, ReadBookConfig.configFileName).takeIf {
diff --git a/app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt b/app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt
index b77e91d68..4edb98624 100644
--- a/app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt
+++ b/app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt
@@ -68,6 +68,8 @@ import androidx.compose.ui.zIndex
import cn.hutool.core.date.DateUtil
import io.legado.app.data.entities.readRecord.ReadRecord
import io.legado.app.data.entities.readRecord.ReadRecordDetail
+import io.legado.app.data.entities.readRecord.ReadRecordSession
+import io.legado.app.ui.book.readRecord.component.*
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveContentPaddingOnlyVertical
import io.legado.app.ui.theme.adaptiveHorizontalPadding
@@ -110,12 +112,18 @@ import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.Date
+data class TimelineItem(
+ val session: ReadRecordSession,
+ val showHeader: Boolean
+)
+
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ReadRecordScreen(
viewModel: ReadRecordViewModel = koinViewModel(),
onBackClick: () -> Unit,
- onBookClick: (String, String) -> Unit
+ onBookClick: (String, String) -> Unit,
+ onSummaryClick: () -> Unit
) {
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
@@ -283,7 +291,7 @@ fun ReadRecordScreen(
)
) {
item(key = "summary_card") {
- SummarySection(state, viewModel)
+ SummarySection(state, viewModel, onSummaryClick)
}
renderListByMode(
displayMode = displayMode,
@@ -446,7 +454,8 @@ fun ReadRecordScreen(
@Composable
fun SummarySection(
state: ReadRecordUiState,
- viewModel: ReadRecordViewModel
+ viewModel: ReadRecordViewModel,
+ onSummaryClick: () -> Unit
) {
val selectedDate = state.selectedDate
@@ -464,7 +473,7 @@ fun SummarySection(
totalTimeMillis = dailyTime,
bookNamesForCover = distinctBooks.take(3),
viewModel = viewModel,
- onClick = { }
+ onClick = onSummaryClick
)
}
} else {
@@ -478,7 +487,7 @@ fun SummarySection(
totalTimeMillis = totalTime,
bookNamesForCover = state.latestRecords.take(5).map { it.bookName to it.bookAuthor },
viewModel = viewModel,
- onClick = { }
+ onClick = onSummaryClick
)
}
}
diff --git a/app/src/main/java/io/legado/app/ui/config/customTheme/CustomThemeScreen.kt b/app/src/main/java/io/legado/app/ui/config/customTheme/CustomThemeScreen.kt
index d4546b037..12295643c 100644
--- a/app/src/main/java/io/legado/app/ui/config/customTheme/CustomThemeScreen.kt
+++ b/app/src/main/java/io/legado/app/ui/config/customTheme/CustomThemeScreen.kt
@@ -44,8 +44,7 @@ import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomThemeScreen(
- onBackClick: () -> Unit,
- onNavigateToThemePack: () -> Unit
+ onBackClick: () -> Unit
) {
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
var showColorPicker by remember { mutableStateOf(false) }
@@ -73,7 +72,7 @@ fun CustomThemeScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
GlassMediumFlexibleTopAppBar(
- title = stringResource(R.string.custom_theme),
+ title = "自定义颜色",
scrollBehavior = scrollBehavior,
navigationIcon = {
TopBarNavigationButton(onClick = onBackClick)
@@ -299,17 +298,6 @@ fun CustomThemeScreen(
}
}
- // Theme Pack entry
- item {
- SplicedColumnGroup {
- ClickableSettingItem(
- title = stringResource(R.string.theme_pack),
- description = stringResource(R.string.theme_pack_s),
- onClick = onNavigateToThemePack
- )
- }
- }
-
}
// Deep personalization color picker
diff --git a/app/src/main/java/io/legado/app/ui/config/themeConfig/NavIconManageSheet.kt b/app/src/main/java/io/legado/app/ui/config/themeConfig/NavIconManageSheet.kt
index f9f418c45..a0d2599a4 100644
--- a/app/src/main/java/io/legado/app/ui/config/themeConfig/NavIconManageSheet.kt
+++ b/app/src/main/java/io/legado/app/ui/config/themeConfig/NavIconManageSheet.kt
@@ -26,7 +26,6 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
-import io.legado.app.ui.config.mainConfig.MainConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SmallTonalIconButton
import io.legado.app.ui.widget.components.card.NormalCard
@@ -52,10 +51,10 @@ fun NavIconManageSheet(
var activeDest by remember { mutableStateOf(null) }
val destinations = listOf(
- NavIconDestination("bookshelf", "书架", MainConfig.navIconBookshelf) { MainConfig.navIconBookshelf = it },
- NavIconDestination("explore", "发现", MainConfig.navIconExplore) { MainConfig.navIconExplore = it },
- NavIconDestination("rss", "订阅", MainConfig.navIconRss) { MainConfig.navIconRss = it },
- NavIconDestination("my", "我的", MainConfig.navIconMy) { MainConfig.navIconMy = it },
+ NavIconDestination("bookshelf", "书架", ThemeConfig.navIconBookshelf) { ThemeConfig.navIconBookshelf = it },
+ NavIconDestination("explore", "发现", ThemeConfig.navIconExplore) { ThemeConfig.navIconExplore = it },
+ NavIconDestination("rss", "订阅", ThemeConfig.navIconRss) { ThemeConfig.navIconRss = it },
+ NavIconDestination("my", "我的", ThemeConfig.navIconMy) { ThemeConfig.navIconMy = it },
)
val selectImage = rememberLauncherForActivityResult(
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 adcec733e..f31904420 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
@@ -52,6 +52,9 @@ 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
@@ -59,13 +62,13 @@ 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.mainConfig.MainConfig
+import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.config.otherConfig.OtherConfigScreen
import io.legado.app.ui.config.personalizationConfig.FontSelectScreen
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.themePack.ThemePackScreen
+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
@@ -76,6 +79,7 @@ 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
@@ -110,6 +114,8 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
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"
@@ -286,7 +292,7 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
private data object MainRouteSettingsCustomTheme : MainRoute
@Serializable
- private data object MainRouteSettingsThemePack : MainRoute
+ private data object MainRouteSettingsThemeManage : MainRoute
@Serializable
private data object MainRouteImportLocal : MainRoute
@@ -294,6 +300,12 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
@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
@@ -358,7 +370,7 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
override fun Content() {
val orientation = resources.configuration.orientation
val smallestWidthDp = resources.configuration.smallestScreenWidthDp
- val tabletInterface = MainConfig.tabletInterface
+ val tabletInterface = ThemeConfig.tabletInterface
val useRail = when (tabletInterface) {
"always" -> true
@@ -524,6 +536,9 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
)
)
},
+ onNavigateToReadRecord = {
+ navigateToRoute(backStack, MainRouteReadRecord)
+ },
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
)
@@ -555,7 +570,8 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
entry {
ThemeConfigScreen(
onBackClick = { navigateBack(backStack) },
- onNavigateToCustomTheme = { backStack.add(MainRouteSettingsCustomTheme) }
+ onNavigateToCustomTheme = { backStack.add(MainRouteSettingsCustomTheme) },
+ onNavigateToThemeManage = { backStack.add(MainRouteSettingsThemeManage) }
)
}
@@ -565,13 +581,12 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
entry {
CustomThemeScreen(
- onBackClick = { navigateBack(backStack) },
- onNavigateToThemePack = { backStack.add(MainRouteSettingsThemePack) }
+ onBackClick = { navigateBack(backStack) }
)
}
- entry {
- ThemePackScreen(onBackClick = { navigateBack(backStack) })
+ entry {
+ ThemeManageScreen(onBackClick = { navigateBack(backStack) })
}
entry {
@@ -684,6 +699,43 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
)
}
+ 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
@@ -780,7 +832,7 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
MainRouteSettingsTheme,
MainRouteSettingsBackup,
MainRouteSettingsCustomTheme,
- MainRouteSettingsThemePack -> {
+ MainRouteSettingsThemeManage -> {
backStack.clear()
backStack.add(MainRouteHome)
backStack.add(MainRouteSettings)
@@ -853,6 +905,26 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
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)
+ }
+ }
}
}
diff --git a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
index 239d00d45..d0dbf6cc2 100644
--- a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
@@ -7,7 +7,7 @@ import io.legado.app.constant.PreferKey
import io.legado.app.constant.EventBus
import io.legado.app.domain.usecase.AppStartupMaintenanceUseCase
import io.legado.app.domain.usecase.WebDavBackupUseCase
-import io.legado.app.ui.config.mainConfig.MainConfig
+import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.main.my.PrefClickEvent
import io.legado.app.utils.defaultSharedPreferences
import io.legado.app.utils.eventBus.FlowEventBus
@@ -87,7 +87,7 @@ class MainViewModel(
fun setNavExtended(expanded: Boolean) {
if (_uiState.value.navExtended == expanded) return
_uiState.update { it.copy(navExtended = expanded) }
- MainConfig.navExtended = expanded
+ ThemeConfig.navExtended = expanded
}
fun onPrefClickEvent(event: PrefClickEvent) {
@@ -112,6 +112,8 @@ class MainViewModel(
PrefClickEvent.ExitApp -> _effects.tryEmit(MainEffect.ExitApp)
+ PrefClickEvent.OpenReadRecord -> _effects.tryEmit(MainEffect.NavigateToReadRecord)
+
else -> Unit
}
}
@@ -128,6 +130,7 @@ sealed interface MainEffect {
) : MainEffect
data object ExitApp : MainEffect
+ data object NavigateToReadRecord : MainEffect
}
data class MainUiState(
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 43e866b1d..a11fc9ffb 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
@@ -10,6 +10,10 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.clickable
@@ -52,6 +56,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
+import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface
@@ -68,6 +74,8 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.zIndex
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
@@ -91,8 +99,10 @@ import io.legado.app.ui.theme.adaptiveContentPaddingBookshelf
import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.theme.adaptiveHorizontalPaddingTab
import io.legado.app.ui.widget.components.ActionItem
+import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SelectionActions
+import io.legado.app.ui.widget.components.SelectionBottomBar
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.alert.AppAlertDialog
@@ -103,8 +113,9 @@ import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyVerticalGrid
-import io.legado.app.ui.widget.components.list.ListScaffold
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
+import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.tabRow.AppTabRow
@@ -186,14 +197,29 @@ fun BookshelfScreen(
)
if (uiState.groups.isEmpty()) {
- ListScaffold(
- title = uiState.title.ifEmpty { stringResource(R.string.bookshelf) },
- subtitle = uiState.subtitle,
- state = uiState,
- showSearchAction = true,
- onSearchToggle = { viewModel.setSearchMode(it) },
- onSearchQueryChange = { viewModel.setSearchKey(it) },
- snackbarHostState = snackbarHostState
+ val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
+ AppScaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ snackbarHost = {
+ SnackbarHost(
+ hostState = snackbarHostState,
+ modifier = Modifier
+ .padding(bottom = 72.dp)
+ )
+ },
+ topBar = {
+ DynamicTopAppBar(
+ title = uiState.title.ifEmpty { stringResource(R.string.bookshelf) },
+ subtitle = uiState.subtitle,
+ state = uiState,
+ scrollBehavior = scrollBehavior,
+ showSearchAction = true,
+ onSearchToggle = { viewModel.setSearchMode(it) },
+ onSearchQueryChange = { viewModel.setSearchKey(it) },
+ onClearSelection = { viewModel.clearSelection() },
+ searchPlaceholder = "搜索...",
+ )
+ }
) { paddingValues ->
EmptyMessage(
modifier = Modifier
@@ -321,280 +347,235 @@ fun BookshelfScreen(
}
}
- ListScaffold(
- title = uiState.title.ifEmpty { stringResource(R.string.bookshelf) },
- subtitle = uiState.subtitle,
- state = uiState,
- showSearchAction = true,
- onSearchToggle = { active ->
- if (BookshelfConfig.bookshelfSearchActionDirectToSearch) {
- onNavigateToSearch(uiState.searchKey.trim())
- } else {
- viewModel.setSearchMode(active)
- if (!active && uiState.selectedGroupId != currentTabGroupId) {
- viewModel.changeGroup(currentTabGroupId)
- }
- }
+ val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
+
+ AppScaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ snackbarHost = {
+ SnackbarHost(
+ hostState = snackbarHostState,
+ modifier = Modifier
+ .padding(bottom = 72.dp)
+ )
},
- onSearchQueryChange = { viewModel.setSearchKey(it) },
- onSearchSubmit = { rawQuery ->
- rawQuery.trim()
- .takeIf { it.isNotEmpty() }
- ?.let(onNavigateToSearch)
- },
- searchTrailingIcon = {
- if (uiState.searchKey.isNotEmpty()) {
- TopBarActionButton(
- onClick = { viewModel.setSearchKey("") },
- imageVector = AppIcons.Close,
- contentDescription = stringResource(R.string.clear)
- )
- }
- },
- topBarActions = {
- AnimatedVisibility(visible = isEditMode) {
- TopBarActionButton(
- onClick = { viewModel.selectAllVisible() },
- imageVector = Icons.Default.SelectAll,
- contentDescription = stringResource(R.string.select_all)
- )
- }
- AnimatedVisibility(visible = isEditMode) {
- TopBarActionButton(
- onClick = { viewModel.invertVisibleSelection() },
- imageVector = Icons.Default.Refresh,
- contentDescription = stringResource(R.string.revert_selection)
- )
- }
- AnimatedVisibility(visible = isEditMode) {
- TopBarActionButton(
- onClick = {
- if (selectedBookUrls.isNotEmpty()) {
- viewModel.showOverlay(BookshelfOverlay.BatchDownloadConfirmDialog)
- }
- },
- imageVector = Icons.Default.Download,
- contentDescription = stringResource(R.string.action_download)
- )
- }
- AnimatedVisibility(visible = isEditMode) {
- TopBarActionButton(
- onClick = {
- if (selectedBookUrls.isNotEmpty()) {
- viewModel.showOverlay(BookshelfOverlay.GroupSelectSheet)
- }
- },
- imageVector = Icons.Default.Bookmarks,
- contentDescription = stringResource(R.string.move_to_group)
- )
- }
- },
- dropDownMenuContent = if (!isEditMode) {
- { dismiss ->
- RoundDropdownMenuItem(
- text = stringResource(R.string.add_remote_book),
- onClick = { onNavigateToRemoteImport(); dismiss() },
- leadingIcon = { Icon(Icons.Default.Wifi, null) }
- )
- RoundDropdownMenuItem(
- text = stringResource(R.string.book_local),
- onClick = { onNavigateToLocalImport(); dismiss() },
- leadingIcon = { Icon(Icons.Default.Save, null) }
- )
- RoundDropdownMenuItem(
- text = stringResource(R.string.update_toc),
- onClick = { viewModel.upToc(uiState.items); dismiss() },
- leadingIcon = { Icon(Icons.Default.Refresh, null) }
- )
- RoundDropdownMenuItem(
- text = stringResource(R.string.layout_setting),
- onClick = {
- viewModel.showOverlay(BookshelfOverlay.ConfigSheet)
- dismiss()
- },
- leadingIcon = { Icon(Icons.Default.GridView, null) }
- )
- RoundDropdownMenuItem(
- text = stringResource(R.string.group_manage),
- onClick = {
- viewModel.showOverlay(BookshelfOverlay.GroupManageSheet)
- dismiss()
- },
- leadingIcon = { Icon(Icons.Default.Edit, null) }
- )
- RoundDropdownMenuItem(
- text = stringResource(R.string.add_url),
- onClick = {
- viewModel.showOverlay(BookshelfOverlay.AddUrlDialog)
- dismiss()
- },
- leadingIcon = { Icon(Icons.Default.Link, null) }
- )
- RoundDropdownMenuItem(
- text = stringResource(R.string.edit),
- onClick = {
- toggleEditMode()
- dismiss()
- },
- leadingIcon = { Icon(Icons.Default.Edit, null) }
- )
- RoundDropdownMenuItem(
- text = stringResource(R.string.bookshelf_management),
- onClick = {
- val groupId =
- uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
- onNavigateToCache(groupId)
- dismiss()
- },
- leadingIcon = { Icon(Icons.Default.Bookmarks, null) }
- )
- RoundDropdownMenuItem(
- text = stringResource(R.string.export_bookshelf),
- onClick = {
- viewModel.showOverlay(BookshelfOverlay.ExportSheet)
- dismiss()
- },
- leadingIcon = { Icon(Icons.Default.UploadFile, null) }
- )
- RoundDropdownMenuItem(
- text = stringResource(R.string.import_bookshelf),
- onClick = {
- viewModel.showOverlay(BookshelfOverlay.ImportSheet)
- dismiss()
- },
- leadingIcon = { Icon(Icons.Default.CloudDownload, null) }
- )
- RoundDropdownMenuItem(
- text = stringResource(R.string.log),
- onClick = {
- viewModel.showOverlay(BookshelfOverlay.LogSheet)
- dismiss()
- },
- leadingIcon = { Icon(Icons.Default.History, null) }
- )
- }
- } else null,
- selectionActions = if (isEditMode) {
- SelectionActions(
- primaryAction = ActionItem(
- text = stringResource(R.string.action_download),
- icon = { Icon(Icons.Default.Download, contentDescription = null) },
- onClick = {
- if (selectedBookUrls.isNotEmpty()) {
- viewModel.showOverlay(BookshelfOverlay.BatchDownloadConfirmDialog)
+ topBar = {
+ DynamicTopAppBar(
+ title = uiState.title.ifEmpty { stringResource(R.string.bookshelf) },
+ subtitle = uiState.subtitle,
+ state = uiState,
+ scrollBehavior = scrollBehavior,
+ onBackClick = if (isEditMode) exitEditMode else null,
+ backNavigationIcon = AppIcons.Close,
+ showSearchAction = true,
+ onSearchToggle = { active ->
+ if (BookshelfConfig.bookshelfSearchActionDirectToSearch) {
+ onNavigateToSearch(uiState.searchKey.trim())
+ } else {
+ viewModel.setSearchMode(active)
+ if (!active && uiState.selectedGroupId != currentTabGroupId) {
+ viewModel.changeGroup(currentTabGroupId)
}
}
- ),
- secondaryActions = listOf(
- ActionItem(
- text = stringResource(R.string.move_to_group),
- icon = { Icon(Icons.Default.Bookmarks, contentDescription = null) },
- onClick = {
- if (selectedBookUrls.isNotEmpty()) {
- viewModel.showOverlay(BookshelfOverlay.GroupSelectSheet)
- }
- }
- )
- ),
- onClearSelection = { viewModel.clearSelection() },
- onSelectAll = { viewModel.selectAllVisible() },
- onSelectInvert = { viewModel.invertVisibleSelection() }
- )
- } else {
- null
- },
- snackbarHostState = snackbarHostState,
- bottomContent = if (bookGroupStyle == 0) {
- {
- if (uiState.groups.isNotEmpty()) {
- val selectedTabIndex =
- pagerState.currentPage.coerceIn(0, uiState.groups.size - 1)
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .adaptiveHorizontalPaddingTab(),
- verticalAlignment = Alignment.CenterVertically
- ) {
- val tabTitles = remember(uiState.groups) {
- uiState.groups.map { it.groupName }
- }
-
- AppTabRow(
- tabTitles = tabTitles,
- selectedTabIndex = selectedTabIndex,
- onTabSelected = { index ->
- scope.launch { pagerState.animateScrollToPage(index) }
- },
- modifier = Modifier.weight(1f)
+ },
+ onSearchQueryChange = { viewModel.setSearchKey(it) },
+ onSearchSubmit = { rawQuery ->
+ rawQuery.trim()
+ .takeIf { it.isNotEmpty() }
+ ?.let(onNavigateToSearch)
+ },
+ searchTrailingIcon = {
+ if (uiState.searchKey.isNotEmpty()) {
+ TopBarActionButton(
+ onClick = { viewModel.setSearchKey("") },
+ imageVector = AppIcons.Close,
+ contentDescription = stringResource(R.string.clear)
)
+ }
+ },
+ searchPlaceholder = "搜索...",
+ onClearSelection = { viewModel.clearSelection() },
+ topBarActions = {
+ AnimatedVisibility(visible = isEditMode) {
+ TopBarActionButton(
+ onClick = { viewModel.selectAllVisible() },
+ imageVector = Icons.Default.SelectAll,
+ contentDescription = stringResource(R.string.select_all)
+ )
+ }
+ AnimatedVisibility(visible = isEditMode) {
+ TopBarActionButton(
+ onClick = { viewModel.invertVisibleSelection() },
+ imageVector = Icons.Default.Refresh,
+ contentDescription = stringResource(R.string.revert_selection)
+ )
+ }
+ AnimatedVisibility(visible = isEditMode) {
+ TopBarActionButton(
+ onClick = {
+ if (selectedBookUrls.isNotEmpty()) {
+ viewModel.showOverlay(BookshelfOverlay.BatchDownloadConfirmDialog)
+ }
+ },
+ imageVector = Icons.Default.Download,
+ contentDescription = stringResource(R.string.action_download)
+ )
+ }
+ AnimatedVisibility(visible = isEditMode) {
+ TopBarActionButton(
+ onClick = {
+ if (selectedBookUrls.isNotEmpty()) {
+ viewModel.showOverlay(BookshelfOverlay.GroupSelectSheet)
+ }
+ },
+ imageVector = Icons.Default.Bookmarks,
+ contentDescription = stringResource(R.string.move_to_group)
+ )
+ }
+ },
+ dropDownMenuContent = if (!isEditMode) {
+ { dismiss ->
+ RoundDropdownMenuItem(
+ text = stringResource(R.string.add_remote_book),
+ onClick = { onNavigateToRemoteImport(); dismiss() },
+ leadingIcon = { Icon(Icons.Default.Wifi, null) }
+ )
+ RoundDropdownMenuItem(
+ text = stringResource(R.string.book_local),
+ onClick = { onNavigateToLocalImport(); dismiss() },
+ leadingIcon = { Icon(Icons.Default.Save, null) }
+ )
+ RoundDropdownMenuItem(
+ text = stringResource(R.string.update_toc),
+ onClick = { viewModel.upToc(uiState.items); dismiss() },
+ leadingIcon = { Icon(Icons.Default.Refresh, null) }
+ )
+ RoundDropdownMenuItem(
+ text = stringResource(R.string.layout_setting),
+ onClick = {
+ viewModel.showOverlay(BookshelfOverlay.ConfigSheet)
+ dismiss()
+ },
+ leadingIcon = { Icon(Icons.Default.GridView, null) }
+ )
+ RoundDropdownMenuItem(
+ text = stringResource(R.string.group_manage),
+ onClick = {
+ viewModel.showOverlay(BookshelfOverlay.GroupManageSheet)
+ dismiss()
+ },
+ leadingIcon = { Icon(Icons.Default.Edit, null) }
+ )
+ RoundDropdownMenuItem(
+ text = stringResource(R.string.add_url),
+ onClick = {
+ viewModel.showOverlay(BookshelfOverlay.AddUrlDialog)
+ dismiss()
+ },
+ leadingIcon = { Icon(Icons.Default.Link, null) }
+ )
+ RoundDropdownMenuItem(
+ text = stringResource(R.string.edit),
+ onClick = {
+ toggleEditMode()
+ dismiss()
+ },
+ leadingIcon = { Icon(Icons.Default.Edit, null) }
+ )
+ RoundDropdownMenuItem(
+ text = stringResource(R.string.bookshelf_management),
+ onClick = {
+ val groupId =
+ uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
+ onNavigateToCache(groupId)
+ dismiss()
+ },
+ leadingIcon = { Icon(Icons.Default.Bookmarks, null) }
+ )
+ RoundDropdownMenuItem(
+ text = stringResource(R.string.export_bookshelf),
+ onClick = {
+ viewModel.showOverlay(BookshelfOverlay.ExportSheet)
+ dismiss()
+ },
+ leadingIcon = { Icon(Icons.Default.UploadFile, null) }
+ )
+ RoundDropdownMenuItem(
+ text = stringResource(R.string.import_bookshelf),
+ onClick = {
+ viewModel.showOverlay(BookshelfOverlay.ImportSheet)
+ dismiss()
+ },
+ leadingIcon = { Icon(Icons.Default.CloudDownload, null) }
+ )
+ RoundDropdownMenuItem(
+ text = stringResource(R.string.log),
+ onClick = {
+ viewModel.showOverlay(BookshelfOverlay.LogSheet)
+ dismiss()
+ },
+ leadingIcon = { Icon(Icons.Default.History, null) }
+ )
+ }
+ } else null,
+ bottomContent = if (bookGroupStyle == 0) {
+ {
+ if (uiState.groups.isNotEmpty()) {
+ val selectedTabIndex =
+ pagerState.currentPage.coerceIn(0, uiState.groups.size - 1)
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .adaptiveHorizontalPaddingTab(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ val tabTitles = remember(uiState.groups) {
+ uiState.groups.map { it.groupName }
+ }
- if (BookshelfConfig.shouldShowExpandButton) {
- Box(modifier = Modifier) {
- SmallOutlinedIconToggleButton(
- checked = showGroupMenu,
- onCheckedChange = {
- if (it) {
- viewModel.showOverlay(BookshelfOverlay.GroupMenu)
- } else {
- viewModel.dismissOverlay()
- }
+ AppTabRow(
+ tabTitles = tabTitles,
+ selectedTabIndex = selectedTabIndex,
+ onTabSelected = { index ->
+ scope.launch { pagerState.animateScrollToPage(index) }
},
- imageVector = Icons.AutoMirrored.Filled.FormatListBulleted,
- contentDescription = stringResource(R.string.group_manage)
+ modifier = Modifier.weight(1f)
)
- RoundDropdownMenu(
- expanded = showGroupMenu,
- onDismissRequest = { viewModel.dismissOverlay() }
- ) { dismiss ->
- uiState.groups.forEachIndexed { index, group ->
- RoundDropdownMenuItem(
- text = group.groupName,
- onClick = {
- if (uiState.isSearch) {
- viewModel.changeGroup(group.groupId)
- }
- scope.launch { pagerState.animateScrollToPage(index) }
- dismiss()
- },
- trailingIcon = {
- val isSelected = if (uiState.isSearch) {
- uiState.selectedGroupId == group.groupId
+
+ if (BookshelfConfig.shouldShowExpandButton) {
+ Box(modifier = Modifier) {
+ SmallOutlinedIconToggleButton(
+ checked = showGroupMenu,
+ onCheckedChange = {
+ if (it) {
+ viewModel.showOverlay(BookshelfOverlay.GroupMenu)
} else {
- selectedTabIndex == index
+ viewModel.dismissOverlay()
}
- if (isSelected) {
- Icon(
- Icons.Default.Check,
- null,
- modifier = Modifier.size(18.dp)
- )
- }
- }
+ },
+ imageVector = Icons.AutoMirrored.Filled.FormatListBulleted,
+ contentDescription = stringResource(R.string.group_manage)
)
- }
-
- if (uiState.isSearch) {
- val allGroup = uiState.allGroups.firstOrNull {
- it.groupId == BookGroup.IdAll
- }
- val hiddenGroups = uiState.allGroups.filter {
- !it.show && it.groupId != BookGroup.IdAll
- }
-
- if (allGroup != null || hiddenGroups.isNotEmpty()) {
- PillHeaderDivider(
- title = "${stringResource(R.string.all)} / ${stringResource(R.string.hide)}"
- )
-
- allGroup?.let { group ->
+ RoundDropdownMenu(
+ expanded = showGroupMenu,
+ onDismissRequest = { viewModel.dismissOverlay() }
+ ) { dismiss ->
+ uiState.groups.forEachIndexed { index, group ->
RoundDropdownMenuItem(
text = group.groupName,
onClick = {
- viewModel.changeGroup(group.groupId)
+ if (uiState.isSearch) {
+ viewModel.changeGroup(group.groupId)
+ }
+ scope.launch { pagerState.animateScrollToPage(index) }
dismiss()
},
trailingIcon = {
- if (uiState.selectedGroupId == group.groupId) {
+ val isSelected = if (uiState.isSearch) {
+ uiState.selectedGroupId == group.groupId
+ } else {
+ selectedTabIndex == index
+ }
+ if (isSelected) {
Icon(
Icons.Default.Check,
null,
@@ -605,23 +586,57 @@ fun BookshelfScreen(
)
}
- hiddenGroups.forEach { group ->
- RoundDropdownMenuItem(
- text = group.groupName,
- onClick = {
- viewModel.changeGroup(group.groupId)
- dismiss()
- },
- trailingIcon = {
- if (uiState.selectedGroupId == group.groupId) {
- Icon(
- Icons.Default.Check,
- null,
- modifier = Modifier.size(18.dp)
- )
- }
+ if (uiState.isSearch) {
+ val allGroup = uiState.allGroups.firstOrNull {
+ it.groupId == BookGroup.IdAll
+ }
+ val hiddenGroups = uiState.allGroups.filter {
+ !it.show && it.groupId != BookGroup.IdAll
+ }
+
+ if (allGroup != null || hiddenGroups.isNotEmpty()) {
+ PillHeaderDivider(
+ title = "${stringResource(R.string.all)} / ${stringResource(R.string.hide)}"
+ )
+
+ allGroup?.let { group ->
+ RoundDropdownMenuItem(
+ text = group.groupName,
+ onClick = {
+ viewModel.changeGroup(group.groupId)
+ dismiss()
+ },
+ trailingIcon = {
+ if (uiState.selectedGroupId == group.groupId) {
+ Icon(
+ Icons.Default.Check,
+ null,
+ modifier = Modifier.size(18.dp)
+ )
+ }
+ }
+ )
}
- )
+
+ hiddenGroups.forEach { group ->
+ RoundDropdownMenuItem(
+ text = group.groupName,
+ onClick = {
+ viewModel.changeGroup(group.groupId)
+ dismiss()
+ },
+ trailingIcon = {
+ if (uiState.selectedGroupId == group.groupId) {
+ Icon(
+ Icons.Default.Check,
+ null,
+ modifier = Modifier.size(18.dp)
+ )
+ }
+ }
+ )
+ }
+ }
}
}
}
@@ -629,9 +644,9 @@ fun BookshelfScreen(
}
}
}
- }
- }
- } else null
+ } else null
+ )
+ }
) { paddingValues ->
val pullToRefreshState = rememberPullToRefreshState()
val currentGroup by remember {
@@ -883,7 +898,7 @@ fun BookshelfScreen(
}
}
}
-
+
if (summary.showGroupName) {
RoundDropdownMenu(
@@ -934,6 +949,41 @@ fun BookshelfScreen(
.align(Alignment.TopCenter)
.padding(top = paddingValues.calculateTopPadding())
)
+
+ AnimatedVisibility(
+ visible = isEditMode && selectedBookUrls.isNotEmpty(),
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .padding(bottom = 16.dp + ScreenOffset)
+ .zIndex(1f),
+ enter = slideInVertically { it } + fadeIn(),
+ exit = slideOutVertically { it } + fadeOut()
+ ) {
+ SelectionBottomBar(
+ onSelectAll = { viewModel.selectAllVisible() },
+ onSelectInvert = { viewModel.invertVisibleSelection() },
+ primaryAction = ActionItem(
+ text = stringResource(R.string.action_download),
+ icon = { Icon(Icons.Default.Download, contentDescription = null) },
+ onClick = {
+ if (selectedBookUrls.isNotEmpty()) {
+ viewModel.showOverlay(BookshelfOverlay.BatchDownloadConfirmDialog)
+ }
+ }
+ ),
+ secondaryActions = listOf(
+ ActionItem(
+ text = stringResource(R.string.move_to_group),
+ icon = { Icon(Icons.Default.Bookmarks, contentDescription = null) },
+ onClick = {
+ if (selectedBookUrls.isNotEmpty()) {
+ viewModel.showOverlay(BookshelfOverlay.GroupSelectSheet)
+ }
+ }
+ )
+ )
+ )
+ }
}
}
@@ -972,7 +1022,7 @@ private fun BookshelfOverlays(
GroupSelectSheet(
show = activeOverlay == BookshelfOverlay.GroupSelectSheet,
- groups = groups,
+ groups = groups.filter { it.groupId > 0 },
currentGroupId = 0L,
onDismissRequest = { viewModel.dismissOverlay() },
onConfirm = { groupId ->
diff --git a/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt b/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt
index ef893dc75..51eaa1dc4 100644
--- a/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt
+++ b/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt
@@ -47,7 +47,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.ui.about.AboutActivity
import io.legado.app.ui.book.bookmark.AllBookmarkActivity
-import io.legado.app.ui.book.readRecord.ReadRecordActivity
import io.legado.app.ui.book.source.manage.BookSourceActivity
import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
import io.legado.app.ui.dict.rule.DictRuleActivity
@@ -186,7 +185,7 @@ fun MyScreen(
title = stringResource(R.string.read_record),
imageVector = Icons.Default.History,
onClick = {
- onNavigate(PrefClickEvent.StartActivity(ReadRecordActivity::class.java))
+ onNavigate(PrefClickEvent.OpenReadRecord)
}
)
ClickableSettingItem(
diff --git a/app/src/main/java/io/legado/app/ui/main/my/MyViewModel.kt b/app/src/main/java/io/legado/app/ui/main/my/MyViewModel.kt
index 4ef47a16f..290b73b3b 100644
--- a/app/src/main/java/io/legado/app/ui/main/my/MyViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/main/my/MyViewModel.kt
@@ -22,6 +22,7 @@ sealed class PrefClickEvent {
data class CopyUrl(val url: String) : PrefClickEvent()
data class ShowMd(val title: String, val path: String) : PrefClickEvent()
data class StartActivity(val destination: Class<*>, val configTag: String? = null) : PrefClickEvent()
+ object OpenReadRecord : PrefClickEvent()
object OpenBookCacheManage : PrefClickEvent()
object ToggleWebService : PrefClickEvent()
object ExitApp : PrefClickEvent()
diff --git a/app/src/main/java/io/legado/app/ui/widget/components/card/GlassCard.kt b/app/src/main/java/io/legado/app/ui/widget/components/card/GlassCard.kt
index 8c519c25b..827338575 100644
--- a/app/src/main/java/io/legado/app/ui/widget/components/card/GlassCard.kt
+++ b/app/src/main/java/io/legado/app/ui/widget/components/card/GlassCard.kt
@@ -39,7 +39,7 @@ private fun BaseCard(
alpha: Float = 1f,
content: @Composable ColumnScope.() -> Unit
) {
- val resolvedContainerColor = (containerColor ?: LegadoTheme.colorScheme.secondaryContainer)
+ val resolvedContainerColor = (containerColor ?: LegadoTheme.colorScheme.surfaceContainer)
.let { it.copy(alpha = it.alpha * alpha) }
val isTransparent = containerColor == Color.Transparent
val shape = RoundedCornerShape(cornerRadius)
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 851ed60ba..6db85f5eb 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
@@ -262,6 +262,7 @@ fun CompactClickableSettingItem(
imageVector: ImageVector? = null,
color: Color? = MaterialTheme.colorScheme.surface,
shape: Shape = MaterialTheme.shapes.small,
+ trailingContent: (@Composable () -> Unit)? = null,
onClick: () -> Unit
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
@@ -278,7 +279,7 @@ fun CompactClickableSettingItem(
imageVector = imageVector,
color = color,
shape = shape,
- trailingContent = {
+ trailingContent = trailingContent ?: {
Icon(
imageVector = Icons.Default.ChevronRight,
contentDescription = null,
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 62089756e..34fb9a776 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -774,7 +774,12 @@
自动翻页速度
地址排序
本地和 WebDav 一起备份
- 优先从 WebDav 恢复,长按从本地恢复
+ 自动备份同步
+ 选择自动备份时的同步行为
+ 备份至本地与网络
+ 备份至本地
+ 备份至网络
+ 选择从本地或网络恢复备份
选择旧版备份文件夹
已启用
已禁用
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index cbddc3116..b3be3edf3 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -737,7 +737,12 @@
其它
legado-top
本地和WebDav壹起備份
- 優先從WebDav恢復,長按從本地恢復
+ 自動備份同步
+ 選擇自動備份時的同步行為
+ 備份至本地與網絡
+ 備份至本地
+ 備份至網絡
+ 選擇從本地或網絡恢復備份
選擇舊版備份文件夾
已啓用
已禁用
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index aea103b3f..f11827d4d 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -746,7 +746,12 @@
自動翻頁速度
地址排序
本機和 WebDav 一起備份
- 優先從 WebDav 復原,長按從本機復原
+ 自動備份同步
+ 選擇自動備份時的同步行為
+ 備份至本地與網路
+ 備份至本地
+ 備份至網路
+ 選擇從本地或網路恢復備份
選擇舊版備份資料夾
已啟用
已禁用
diff --git a/app/src/main/res/values/array_values.xml b/app/src/main/res/values/array_values.xml
index ba25c9a82..77876517f 100644
--- a/app/src/main/res/values/array_values.xml
+++ b/app/src/main/res/values/array_values.xml
@@ -141,4 +141,10 @@
- High
+
+ - both
+ - local
+ - webdav
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 16fecb080..a114e6862 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -324,5 +324,11 @@
- Maximum
+
+ - @string/backup_to_local_and_network
+ - @string/backup_to_local
+ - @string/backup_to_network
+
+
All Version
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 35fb56585..acace5f86 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -804,7 +804,12 @@
Auto scroll speed
Sort by URL
Backup the local and WebDAV simultaneously
- Restore from WebDAV first, Restore form the local backup on long click
+ Auto Backup Sync
+ Choose sync behavior for automatic backup
+ Backup to Local & Network
+ Backup to Local
+ Backup to Network
+ Choose to restore from local or network backup
Select a legacy backup folder
Enabled
Disabled