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