代码优化
This commit is contained in:
@@ -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)
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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 & 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>
|
||||
|
||||
Reference in New Issue
Block a user