代码优化

This commit is contained in:
HapeLee
2026-05-12 19:17:14 +08:00
parent 890ab3a502
commit b3a294ff1e
22 changed files with 499 additions and 494 deletions
+6 -1
View File
@@ -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 *)"
]
}
}
-6
View File
@@ -291,12 +291,6 @@
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true"
android:theme="@style/AppTheme.Transparent" />
<!-- 阅读记录 -->
<activity
android:name=".ui.book.readRecord.ReadRecordActivity"
android:enableOnBackInvokedCallback="true"
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true" />
<!-- 字典管理 -->
<activity
android:name=".ui.dict.rule.DictRuleActivity"
@@ -257,6 +257,7 @@ object PreferKey {
const val ignoreBatteryPermission = "ignoreBatteryPermission"
const val paddingDisplayCutouts = "paddingDisplayCutouts"
const val autoCheckNewBackup = "autoCheckNewBackup"
const val backupSyncMode = "backupSyncMode"
const val delayBookLoadEnable = "delayBookLoadEnable"
const val sharedElementEnterTransitionEnable = "sharedElementEnterTransitionEnable"
const val bookshelfLayoutGridLandscape = "bookshelfLayoutGridLandscape"
@@ -301,6 +302,8 @@ object PreferKey {
const val showTip = "showTip"
const val showBookCount = "showBookCount"
const val showBookIntro = "showBookIntro"
const val bookshelfShowIntro = "bookshelfShowIntro"
const val bookshelfShowTag = "bookshelfShowTag"
const val bookshelfIntroMaxLines = "bookshelfIntroMaxLines"
const val sliderVibrator = "sliderVibrator"
const val selectVibrator = "selectVibrator"
@@ -48,6 +48,7 @@ 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
@@ -73,6 +74,7 @@ import io.legado.app.ui.book.info.BookInfoViewModel
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.search.SearchViewModel
import io.legado.app.ui.book.searchContent.SearchContentViewModel
import io.legado.app.ui.book.toc.TocViewModel
@@ -128,6 +130,7 @@ val appModule = module {
singleOf(::UpdateBooksGroupUseCase)
singleOf(::UploadReadingProgressUseCase)
singleOf(::ResolveBookShelfStateUseCase)
factory { GetReadRecordOverviewUseCase() }
singleOf(::ShrinkDatabaseUseCase)
singleOf(::WebDavBackupUseCase)
singleOf(::BookshelfManageScreenConfig)
@@ -174,6 +177,7 @@ val appModule = module {
viewModelOf(::RssArticlesViewModel)
viewModelOf(::ReadRssViewModel)
viewModelOf(::ReadRecordViewModel)
viewModelOf(::ReadRecordOverviewViewModel)
viewModelOf(::ExploreShowViewModel)
viewModelOf(::MyViewModel)
viewModelOf(::BookshelfViewModel)
@@ -1,144 +0,0 @@
package io.legado.app.help.config
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import io.legado.app.utils.FileUtils
import io.legado.app.utils.getString
import splitties.init.appCtx
import java.io.File
import java.lang.reflect.Type
object PersonalizationThemeConfig {
const val configFileName = "personalizationThemeConfig.json"
val configFilePath = FileUtils.getPath(appCtx.filesDir, configFileName)
private val THEME_GSON: Gson by lazy {
GsonBuilder()
.registerTypeAdapter(Config::class.java, ConfigSerializer())
.setPrettyPrinting()
.disableHtmlEscaping()
.create()
}
private val _configList: ArrayList<Config> by lazy {
ArrayList(getConfigs() ?: emptyList())
}
val configList: List<Config> 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<Config>::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<Config>? {
val configFile = File(configFilePath)
if (configFile.exists()) {
kotlin.runCatching {
val json = configFile.readText()
return THEME_GSON.fromJson(json, Array<Config>::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<PersonalizationThemeConfig.Config>,
JsonDeserializer<PersonalizationThemeConfig.Config> {
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)
)
}
}
@@ -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 {
@@ -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
)
}
}
@@ -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
@@ -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<String?>(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(
@@ -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<MainRouteSettingsTheme> {
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<MainRouteSettingsCustomTheme> {
CustomThemeScreen(
onBackClick = { navigateBack(backStack) },
onNavigateToThemePack = { backStack.add(MainRouteSettingsThemePack) }
onBackClick = { navigateBack(backStack) }
)
}
entry<MainRouteSettingsThemePack> {
ThemePackScreen(onBackClick = { navigateBack(backStack) })
entry<MainRouteSettingsThemeManage> {
ThemeManageScreen(onBackClick = { navigateBack(backStack) })
}
entry<MainRouteImportLocal> {
@@ -684,6 +699,43 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
)
}
entry<MainRouteReadRecord> {
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<MainRouteReadRecordOverview> {
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<MainRouteBookInfo>(
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)
}
}
}
}
@@ -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(
@@ -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 ->
@@ -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(
@@ -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()
@@ -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)
@@ -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,
+6 -1
View File
@@ -774,7 +774,12 @@
<string name="auto_page_speed">自动翻页速度</string>
<string name="sort_by_url">地址排序</string>
<string name="backup_summary">本地和 WebDav 一起备份</string>
<string name="restore_summary">优先从 WebDav 恢复,长按从本地恢复</string>
<string name="backup_sync_mode">自动备份同步</string>
<string name="backup_sync_mode_summary">选择自动备份时的同步行为</string>
<string name="backup_to_local_and_network">备份至本地与网络</string>
<string name="backup_to_local">备份至本地</string>
<string name="backup_to_network">备份至网络</string>
<string name="restore_summary">选择从本地或网络恢复备份</string>
<string name="import_old_summary">选择旧版备份文件夹</string>
<string name="enabled">已启用</string>
<string name="disabled">已禁用</string>
+6 -1
View File
@@ -737,7 +737,12 @@
<string name="other">其它</string>
<string name="official_account">legado-top</string>
<string name="backup_summary">本地和WebDav壹起備份</string>
<string name="restore_summary">優先從WebDav恢復,長按從本地恢復</string>
<string name="backup_sync_mode">自動備份同步</string>
<string name="backup_sync_mode_summary">選擇自動備份時的同步行為</string>
<string name="backup_to_local_and_network">備份至本地與網絡</string>
<string name="backup_to_local">備份至本地</string>
<string name="backup_to_network">備份至網絡</string>
<string name="restore_summary">選擇從本地或網絡恢復備份</string>
<string name="import_old_summary">選擇舊版備份文件夾</string>
<string name="enabled">已啓用</string>
<string name="disabled">已禁用</string>
+6 -1
View File
@@ -746,7 +746,12 @@
<string name="auto_page_speed">自動翻頁速度</string>
<string name="sort_by_url">地址排序</string>
<string name="backup_summary">本機和 WebDav 一起備份</string>
<string name="restore_summary">優先從 WebDav 復原,長按從本機復原</string>
<string name="backup_sync_mode">自動備份同步</string>
<string name="backup_sync_mode_summary">選擇自動備份時的同步行為</string>
<string name="backup_to_local_and_network">備份至本地與網路</string>
<string name="backup_to_local">備份至本地</string>
<string name="backup_to_network">備份至網路</string>
<string name="restore_summary">選擇從本地或網路恢復備份</string>
<string name="import_old_summary">選擇舊版備份資料夾</string>
<string name="enabled">已啟用</string>
<string name="disabled">已禁用</string>
+6
View File
@@ -141,4 +141,10 @@
<item>High</item>
</string-array>
<string-array name="backup_sync_mode_value">
<item>both</item>
<item>local</item>
<item>webdav</item>
</string-array>
</resources>
+6
View File
@@ -324,5 +324,11 @@
<item>Maximum</item>
</string-array>
<string-array name="backup_sync_mode">
<item>@string/backup_to_local_and_network</item>
<item>@string/backup_to_local</item>
<item>@string/backup_to_network</item>
</string-array>
<string name="all_version">All Version</string>
</resources>
+6 -1
View File
@@ -804,7 +804,12 @@
<string name="auto_page_speed">Auto scroll speed</string>
<string name="sort_by_url">Sort by URL</string>
<string name="backup_summary">Backup the local and WebDAV simultaneously</string>
<string name="restore_summary">Restore from WebDAV first, Restore form the local backup on long click</string>
<string name="backup_sync_mode">Auto Backup Sync</string>
<string name="backup_sync_mode_summary">Choose sync behavior for automatic backup</string>
<string name="backup_to_local_and_network">Backup to Local &amp; Network</string>
<string name="backup_to_local">Backup to Local</string>
<string name="backup_to_network">Backup to Network</string>
<string name="restore_summary">Choose to restore from local or network backup</string>
<string name="import_old_summary">Select a legacy backup folder</string>
<string name="enabled">Enabled</string>
<string name="disabled">Disabled</string>