代码优化
This commit is contained in:
@@ -50,7 +50,12 @@
|
|||||||
"Bash(npx skills *)",
|
"Bash(npx skills *)",
|
||||||
"Bash(npx *)",
|
"Bash(npx *)",
|
||||||
"PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 20)",
|
"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 *)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,12 +291,6 @@
|
|||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:theme="@style/AppTheme.Transparent" />
|
android:theme="@style/AppTheme.Transparent" />
|
||||||
<!-- 阅读记录 -->
|
|
||||||
<activity
|
|
||||||
android:name=".ui.book.readRecord.ReadRecordActivity"
|
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
android:configChanges="orientation|screenSize"
|
|
||||||
android:hardwareAccelerated="true" />
|
|
||||||
<!-- 字典管理 -->
|
<!-- 字典管理 -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.dict.rule.DictRuleActivity"
|
android:name=".ui.dict.rule.DictRuleActivity"
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ object PreferKey {
|
|||||||
const val ignoreBatteryPermission = "ignoreBatteryPermission"
|
const val ignoreBatteryPermission = "ignoreBatteryPermission"
|
||||||
const val paddingDisplayCutouts = "paddingDisplayCutouts"
|
const val paddingDisplayCutouts = "paddingDisplayCutouts"
|
||||||
const val autoCheckNewBackup = "autoCheckNewBackup"
|
const val autoCheckNewBackup = "autoCheckNewBackup"
|
||||||
|
const val backupSyncMode = "backupSyncMode"
|
||||||
const val delayBookLoadEnable = "delayBookLoadEnable"
|
const val delayBookLoadEnable = "delayBookLoadEnable"
|
||||||
const val sharedElementEnterTransitionEnable = "sharedElementEnterTransitionEnable"
|
const val sharedElementEnterTransitionEnable = "sharedElementEnterTransitionEnable"
|
||||||
const val bookshelfLayoutGridLandscape = "bookshelfLayoutGridLandscape"
|
const val bookshelfLayoutGridLandscape = "bookshelfLayoutGridLandscape"
|
||||||
@@ -301,6 +302,8 @@ object PreferKey {
|
|||||||
const val showTip = "showTip"
|
const val showTip = "showTip"
|
||||||
const val showBookCount = "showBookCount"
|
const val showBookCount = "showBookCount"
|
||||||
const val showBookIntro = "showBookIntro"
|
const val showBookIntro = "showBookIntro"
|
||||||
|
const val bookshelfShowIntro = "bookshelfShowIntro"
|
||||||
|
const val bookshelfShowTag = "bookshelfShowTag"
|
||||||
const val bookshelfIntroMaxLines = "bookshelfIntroMaxLines"
|
const val bookshelfIntroMaxLines = "bookshelfIntroMaxLines"
|
||||||
const val sliderVibrator = "sliderVibrator"
|
const val sliderVibrator = "sliderVibrator"
|
||||||
const val selectVibrator = "selectVibrator"
|
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.domain.usecase.RemoveBookGroupAssignmentUseCase
|
||||||
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
|
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
|
||||||
import io.legado.app.domain.usecase.ResolveBookShelfStateUseCase
|
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.SearchBooksUseCase
|
||||||
import io.legado.app.domain.usecase.ShrinkDatabaseUseCase
|
import io.legado.app.domain.usecase.ShrinkDatabaseUseCase
|
||||||
import io.legado.app.domain.usecase.UpdateBooksGroupUseCase
|
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.manga.ReadMangaViewModel
|
||||||
import io.legado.app.ui.book.read.ReadBookViewModel
|
import io.legado.app.ui.book.read.ReadBookViewModel
|
||||||
import io.legado.app.ui.book.readRecord.ReadRecordViewModel
|
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.search.SearchViewModel
|
||||||
import io.legado.app.ui.book.searchContent.SearchContentViewModel
|
import io.legado.app.ui.book.searchContent.SearchContentViewModel
|
||||||
import io.legado.app.ui.book.toc.TocViewModel
|
import io.legado.app.ui.book.toc.TocViewModel
|
||||||
@@ -128,6 +130,7 @@ val appModule = module {
|
|||||||
singleOf(::UpdateBooksGroupUseCase)
|
singleOf(::UpdateBooksGroupUseCase)
|
||||||
singleOf(::UploadReadingProgressUseCase)
|
singleOf(::UploadReadingProgressUseCase)
|
||||||
singleOf(::ResolveBookShelfStateUseCase)
|
singleOf(::ResolveBookShelfStateUseCase)
|
||||||
|
factory { GetReadRecordOverviewUseCase() }
|
||||||
singleOf(::ShrinkDatabaseUseCase)
|
singleOf(::ShrinkDatabaseUseCase)
|
||||||
singleOf(::WebDavBackupUseCase)
|
singleOf(::WebDavBackupUseCase)
|
||||||
singleOf(::BookshelfManageScreenConfig)
|
singleOf(::BookshelfManageScreenConfig)
|
||||||
@@ -174,6 +177,7 @@ val appModule = module {
|
|||||||
viewModelOf(::RssArticlesViewModel)
|
viewModelOf(::RssArticlesViewModel)
|
||||||
viewModelOf(::ReadRssViewModel)
|
viewModelOf(::ReadRssViewModel)
|
||||||
viewModelOf(::ReadRecordViewModel)
|
viewModelOf(::ReadRecordViewModel)
|
||||||
|
viewModelOf(::ReadRecordOverviewViewModel)
|
||||||
viewModelOf(::ExploreShowViewModel)
|
viewModelOf(::ExploreShowViewModel)
|
||||||
viewModelOf(::MyViewModel)
|
viewModelOf(::MyViewModel)
|
||||||
viewModelOf(::BookshelfViewModel)
|
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.book.upType
|
||||||
import io.legado.app.help.config.LocalConfig
|
import io.legado.app.help.config.LocalConfig
|
||||||
import io.legado.app.help.config.OldThemeConfig
|
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.help.config.ReadBookConfig
|
||||||
import io.legado.app.model.BookCover
|
import io.legado.app.model.BookCover
|
||||||
import io.legado.app.model.localBook.LocalBook
|
import io.legado.app.model.localBook.LocalBook
|
||||||
@@ -284,16 +283,6 @@ object Restore {
|
|||||||
}?.onFailure {
|
}?.onFailure {
|
||||||
AppLog.put("恢复封面规则出错\n${it.localizedMessage}", it)
|
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) {
|
if (!BackupConfig.ignoreReadConfig) {
|
||||||
//恢复阅读界面配置
|
//恢复阅读界面配置
|
||||||
File(path, ReadBookConfig.configFileName).takeIf {
|
File(path, ReadBookConfig.configFileName).takeIf {
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ import androidx.compose.ui.zIndex
|
|||||||
import cn.hutool.core.date.DateUtil
|
import cn.hutool.core.date.DateUtil
|
||||||
import io.legado.app.data.entities.readRecord.ReadRecord
|
import io.legado.app.data.entities.readRecord.ReadRecord
|
||||||
import io.legado.app.data.entities.readRecord.ReadRecordDetail
|
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.LegadoTheme
|
||||||
import io.legado.app.ui.theme.adaptiveContentPaddingOnlyVertical
|
import io.legado.app.ui.theme.adaptiveContentPaddingOnlyVertical
|
||||||
import io.legado.app.ui.theme.adaptiveHorizontalPadding
|
import io.legado.app.ui.theme.adaptiveHorizontalPadding
|
||||||
@@ -110,12 +112,18 @@ import java.time.LocalDate
|
|||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
|
data class TimelineItem(
|
||||||
|
val session: ReadRecordSession,
|
||||||
|
val showHeader: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReadRecordScreen(
|
fun ReadRecordScreen(
|
||||||
viewModel: ReadRecordViewModel = koinViewModel(),
|
viewModel: ReadRecordViewModel = koinViewModel(),
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
onBookClick: (String, String) -> Unit
|
onBookClick: (String, String) -> Unit,
|
||||||
|
onSummaryClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -283,7 +291,7 @@ fun ReadRecordScreen(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
item(key = "summary_card") {
|
item(key = "summary_card") {
|
||||||
SummarySection(state, viewModel)
|
SummarySection(state, viewModel, onSummaryClick)
|
||||||
}
|
}
|
||||||
renderListByMode(
|
renderListByMode(
|
||||||
displayMode = displayMode,
|
displayMode = displayMode,
|
||||||
@@ -446,7 +454,8 @@ fun ReadRecordScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
fun SummarySection(
|
fun SummarySection(
|
||||||
state: ReadRecordUiState,
|
state: ReadRecordUiState,
|
||||||
viewModel: ReadRecordViewModel
|
viewModel: ReadRecordViewModel,
|
||||||
|
onSummaryClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
val selectedDate = state.selectedDate
|
val selectedDate = state.selectedDate
|
||||||
|
|
||||||
@@ -464,7 +473,7 @@ fun SummarySection(
|
|||||||
totalTimeMillis = dailyTime,
|
totalTimeMillis = dailyTime,
|
||||||
bookNamesForCover = distinctBooks.take(3),
|
bookNamesForCover = distinctBooks.take(3),
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onClick = { }
|
onClick = onSummaryClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -478,7 +487,7 @@ fun SummarySection(
|
|||||||
totalTimeMillis = totalTime,
|
totalTimeMillis = totalTime,
|
||||||
bookNamesForCover = state.latestRecords.take(5).map { it.bookName to it.bookAuthor },
|
bookNamesForCover = state.latestRecords.take(5).map { it.bookName to it.bookAuthor },
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onClick = { }
|
onClick = onSummaryClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,7 @@ import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CustomThemeScreen(
|
fun CustomThemeScreen(
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit
|
||||||
onNavigateToThemePack: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
|
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
|
||||||
var showColorPicker by remember { mutableStateOf(false) }
|
var showColorPicker by remember { mutableStateOf(false) }
|
||||||
@@ -73,7 +72,7 @@ fun CustomThemeScreen(
|
|||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
topBar = {
|
topBar = {
|
||||||
GlassMediumFlexibleTopAppBar(
|
GlassMediumFlexibleTopAppBar(
|
||||||
title = stringResource(R.string.custom_theme),
|
title = "自定义颜色",
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
TopBarNavigationButton(onClick = onBackClick)
|
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
|
// Deep personalization color picker
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import io.legado.app.ui.config.mainConfig.MainConfig
|
|
||||||
import io.legado.app.ui.theme.LegadoTheme
|
import io.legado.app.ui.theme.LegadoTheme
|
||||||
import io.legado.app.ui.widget.components.button.SmallTonalIconButton
|
import io.legado.app.ui.widget.components.button.SmallTonalIconButton
|
||||||
import io.legado.app.ui.widget.components.card.NormalCard
|
import io.legado.app.ui.widget.components.card.NormalCard
|
||||||
@@ -52,10 +51,10 @@ fun NavIconManageSheet(
|
|||||||
var activeDest by remember { mutableStateOf<String?>(null) }
|
var activeDest by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
val destinations = listOf(
|
val destinations = listOf(
|
||||||
NavIconDestination("bookshelf", "书架", MainConfig.navIconBookshelf) { MainConfig.navIconBookshelf = it },
|
NavIconDestination("bookshelf", "书架", ThemeConfig.navIconBookshelf) { ThemeConfig.navIconBookshelf = it },
|
||||||
NavIconDestination("explore", "发现", MainConfig.navIconExplore) { MainConfig.navIconExplore = it },
|
NavIconDestination("explore", "发现", ThemeConfig.navIconExplore) { ThemeConfig.navIconExplore = it },
|
||||||
NavIconDestination("rss", "订阅", MainConfig.navIconRss) { MainConfig.navIconRss = it },
|
NavIconDestination("rss", "订阅", ThemeConfig.navIconRss) { ThemeConfig.navIconRss = it },
|
||||||
NavIconDestination("my", "我的", MainConfig.navIconMy) { MainConfig.navIconMy = it },
|
NavIconDestination("my", "我的", ThemeConfig.navIconMy) { ThemeConfig.navIconMy = it },
|
||||||
)
|
)
|
||||||
|
|
||||||
val selectImage = rememberLauncherForActivityResult(
|
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.SearchIntent
|
||||||
import io.legado.app.ui.book.search.SearchScreen
|
import io.legado.app.ui.book.search.SearchScreen
|
||||||
import io.legado.app.ui.book.search.SearchViewModel
|
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.source.manage.BookSourceActivity
|
||||||
import io.legado.app.ui.book.manage.BookshelfManageRouteScreen
|
import io.legado.app.ui.book.manage.BookshelfManageRouteScreen
|
||||||
import io.legado.app.ui.book.read.ReadBookActivity
|
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.ConfigTag
|
||||||
import io.legado.app.ui.config.backupConfig.BackupConfigScreen
|
import io.legado.app.ui.config.backupConfig.BackupConfigScreen
|
||||||
import io.legado.app.ui.config.coverConfig.CoverConfigScreen
|
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.otherConfig.OtherConfigScreen
|
||||||
import io.legado.app.ui.config.personalizationConfig.FontSelectScreen
|
import io.legado.app.ui.config.personalizationConfig.FontSelectScreen
|
||||||
import io.legado.app.ui.config.customTheme.CustomThemeScreen
|
import io.legado.app.ui.config.customTheme.CustomThemeScreen
|
||||||
import io.legado.app.ui.config.readConfig.ReadConfigScreen
|
import io.legado.app.ui.config.readConfig.ReadConfigScreen
|
||||||
import io.legado.app.ui.config.themeConfig.ThemeConfigScreen
|
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.MainRouteRssSort
|
||||||
import io.legado.app.ui.rss.article.RssSortRouteScreen
|
import io.legado.app.ui.rss.article.RssSortRouteScreen
|
||||||
import io.legado.app.ui.rss.read.MainRouteRssRead
|
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.getPrefBoolean
|
||||||
import io.legado.app.utils.showDialogFragment
|
import io.legado.app.utils.showDialogFragment
|
||||||
import io.legado.app.utils.startActivity
|
import io.legado.app.utils.startActivity
|
||||||
|
import io.legado.app.utils.startActivityForBook
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
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_EXPLORE_SHOW = "explore/show"
|
||||||
private const val ROUTE_RSS_SORT = "rss/sort"
|
private const val ROUTE_RSS_SORT = "rss/sort"
|
||||||
private const val ROUTE_RSS_READ = "rss/read"
|
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_CACHE_GROUP_ID = "extra_cache_group_id"
|
||||||
private const val EXTRA_SEARCH_KEY = "extra_search_key"
|
private const val EXTRA_SEARCH_KEY = "extra_search_key"
|
||||||
private const val EXTRA_SEARCH_SCOPE = "extra_search_scope"
|
private const val EXTRA_SEARCH_SCOPE = "extra_search_scope"
|
||||||
@@ -286,7 +292,7 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
|
|||||||
private data object MainRouteSettingsCustomTheme : MainRoute
|
private data object MainRouteSettingsCustomTheme : MainRoute
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
private data object MainRouteSettingsThemePack : MainRoute
|
private data object MainRouteSettingsThemeManage : MainRoute
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
private data object MainRouteImportLocal : MainRoute
|
private data object MainRouteImportLocal : MainRoute
|
||||||
@@ -294,6 +300,12 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
|
|||||||
@Serializable
|
@Serializable
|
||||||
private data object MainRouteImportRemote : MainRoute
|
private data object MainRouteImportRemote : MainRoute
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
private data object MainRouteReadRecord : MainRoute
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
private data object MainRouteReadRecordOverview : MainRoute
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
private data class MainRouteCache(val groupId: Long) : MainRoute
|
private data class MainRouteCache(val groupId: Long) : MainRoute
|
||||||
|
|
||||||
@@ -358,7 +370,7 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val orientation = resources.configuration.orientation
|
val orientation = resources.configuration.orientation
|
||||||
val smallestWidthDp = resources.configuration.smallestScreenWidthDp
|
val smallestWidthDp = resources.configuration.smallestScreenWidthDp
|
||||||
val tabletInterface = MainConfig.tabletInterface
|
val tabletInterface = ThemeConfig.tabletInterface
|
||||||
|
|
||||||
val useRail = when (tabletInterface) {
|
val useRail = when (tabletInterface) {
|
||||||
"always" -> true
|
"always" -> true
|
||||||
@@ -524,6 +536,9 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
onNavigateToReadRecord = {
|
||||||
|
navigateToRoute(backStack, MainRouteReadRecord)
|
||||||
|
},
|
||||||
sharedTransitionScope = this@SharedTransitionLayout,
|
sharedTransitionScope = this@SharedTransitionLayout,
|
||||||
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
|
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
|
||||||
)
|
)
|
||||||
@@ -555,7 +570,8 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
|
|||||||
entry<MainRouteSettingsTheme> {
|
entry<MainRouteSettingsTheme> {
|
||||||
ThemeConfigScreen(
|
ThemeConfigScreen(
|
||||||
onBackClick = { navigateBack(backStack) },
|
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> {
|
entry<MainRouteSettingsCustomTheme> {
|
||||||
CustomThemeScreen(
|
CustomThemeScreen(
|
||||||
onBackClick = { navigateBack(backStack) },
|
onBackClick = { navigateBack(backStack) }
|
||||||
onNavigateToThemePack = { backStack.add(MainRouteSettingsThemePack) }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
entry<MainRouteSettingsThemePack> {
|
entry<MainRouteSettingsThemeManage> {
|
||||||
ThemePackScreen(onBackClick = { navigateBack(backStack) })
|
ThemeManageScreen(onBackClick = { navigateBack(backStack) })
|
||||||
}
|
}
|
||||||
|
|
||||||
entry<MainRouteImportLocal> {
|
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>(
|
entry<MainRouteBookInfo>(
|
||||||
metadata = NavDisplay.transitionSpec {
|
metadata = NavDisplay.transitionSpec {
|
||||||
val from = initialState.key
|
val from = initialState.key
|
||||||
@@ -780,7 +832,7 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
|
|||||||
MainRouteSettingsTheme,
|
MainRouteSettingsTheme,
|
||||||
MainRouteSettingsBackup,
|
MainRouteSettingsBackup,
|
||||||
MainRouteSettingsCustomTheme,
|
MainRouteSettingsCustomTheme,
|
||||||
MainRouteSettingsThemePack -> {
|
MainRouteSettingsThemeManage -> {
|
||||||
backStack.clear()
|
backStack.clear()
|
||||||
backStack.add(MainRouteHome)
|
backStack.add(MainRouteHome)
|
||||||
backStack.add(MainRouteSettings)
|
backStack.add(MainRouteSettings)
|
||||||
@@ -853,6 +905,26 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
|
|||||||
backStack.add(route)
|
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.constant.EventBus
|
||||||
import io.legado.app.domain.usecase.AppStartupMaintenanceUseCase
|
import io.legado.app.domain.usecase.AppStartupMaintenanceUseCase
|
||||||
import io.legado.app.domain.usecase.WebDavBackupUseCase
|
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.ui.main.my.PrefClickEvent
|
||||||
import io.legado.app.utils.defaultSharedPreferences
|
import io.legado.app.utils.defaultSharedPreferences
|
||||||
import io.legado.app.utils.eventBus.FlowEventBus
|
import io.legado.app.utils.eventBus.FlowEventBus
|
||||||
@@ -87,7 +87,7 @@ class MainViewModel(
|
|||||||
fun setNavExtended(expanded: Boolean) {
|
fun setNavExtended(expanded: Boolean) {
|
||||||
if (_uiState.value.navExtended == expanded) return
|
if (_uiState.value.navExtended == expanded) return
|
||||||
_uiState.update { it.copy(navExtended = expanded) }
|
_uiState.update { it.copy(navExtended = expanded) }
|
||||||
MainConfig.navExtended = expanded
|
ThemeConfig.navExtended = expanded
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPrefClickEvent(event: PrefClickEvent) {
|
fun onPrefClickEvent(event: PrefClickEvent) {
|
||||||
@@ -112,6 +112,8 @@ class MainViewModel(
|
|||||||
|
|
||||||
PrefClickEvent.ExitApp -> _effects.tryEmit(MainEffect.ExitApp)
|
PrefClickEvent.ExitApp -> _effects.tryEmit(MainEffect.ExitApp)
|
||||||
|
|
||||||
|
PrefClickEvent.OpenReadRecord -> _effects.tryEmit(MainEffect.NavigateToReadRecord)
|
||||||
|
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,6 +130,7 @@ sealed interface MainEffect {
|
|||||||
) : MainEffect
|
) : MainEffect
|
||||||
|
|
||||||
data object ExitApp : MainEffect
|
data object ExitApp : MainEffect
|
||||||
|
data object NavigateToReadRecord : MainEffect
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MainUiState(
|
data class MainUiState(
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.AnimatedVisibilityScope
|
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.ExperimentalSharedTransitionApi
|
||||||
import androidx.compose.animation.SharedTransitionScope
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -52,6 +56,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
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.SnackbarHostState
|
||||||
import androidx.compose.material3.SnackbarResult
|
import androidx.compose.material3.SnackbarResult
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
@@ -68,6 +74,8 @@ import androidx.compose.runtime.rememberUpdatedState
|
|||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.ClipEntry
|
import androidx.compose.ui.platform.ClipEntry
|
||||||
import androidx.compose.ui.platform.LocalClipboard
|
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.adaptiveHorizontalPadding
|
||||||
import io.legado.app.ui.theme.adaptiveHorizontalPaddingTab
|
import io.legado.app.ui.theme.adaptiveHorizontalPaddingTab
|
||||||
import io.legado.app.ui.widget.components.ActionItem
|
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.EmptyMessage
|
||||||
import io.legado.app.ui.widget.components.SelectionActions
|
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.button.SmallOutlinedIconToggleButton
|
||||||
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
|
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
|
||||||
import io.legado.app.ui.widget.components.alert.AppAlertDialog
|
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.icon.AppIcons
|
||||||
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
|
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.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.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.RoundDropdownMenu
|
||||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||||
import io.legado.app.ui.widget.components.tabRow.AppTabRow
|
import io.legado.app.ui.widget.components.tabRow.AppTabRow
|
||||||
@@ -186,14 +197,29 @@ fun BookshelfScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (uiState.groups.isEmpty()) {
|
if (uiState.groups.isEmpty()) {
|
||||||
ListScaffold(
|
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
|
||||||
title = uiState.title.ifEmpty { stringResource(R.string.bookshelf) },
|
AppScaffold(
|
||||||
subtitle = uiState.subtitle,
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
state = uiState,
|
snackbarHost = {
|
||||||
showSearchAction = true,
|
SnackbarHost(
|
||||||
onSearchToggle = { viewModel.setSearchMode(it) },
|
hostState = snackbarHostState,
|
||||||
onSearchQueryChange = { viewModel.setSearchKey(it) },
|
modifier = Modifier
|
||||||
snackbarHostState = snackbarHostState
|
.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 ->
|
) { paddingValues ->
|
||||||
EmptyMessage(
|
EmptyMessage(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -321,280 +347,235 @@ fun BookshelfScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListScaffold(
|
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
|
||||||
title = uiState.title.ifEmpty { stringResource(R.string.bookshelf) },
|
|
||||||
subtitle = uiState.subtitle,
|
AppScaffold(
|
||||||
state = uiState,
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
showSearchAction = true,
|
snackbarHost = {
|
||||||
onSearchToggle = { active ->
|
SnackbarHost(
|
||||||
if (BookshelfConfig.bookshelfSearchActionDirectToSearch) {
|
hostState = snackbarHostState,
|
||||||
onNavigateToSearch(uiState.searchKey.trim())
|
modifier = Modifier
|
||||||
} else {
|
.padding(bottom = 72.dp)
|
||||||
viewModel.setSearchMode(active)
|
)
|
||||||
if (!active && uiState.selectedGroupId != currentTabGroupId) {
|
|
||||||
viewModel.changeGroup(currentTabGroupId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onSearchQueryChange = { viewModel.setSearchKey(it) },
|
topBar = {
|
||||||
onSearchSubmit = { rawQuery ->
|
DynamicTopAppBar(
|
||||||
rawQuery.trim()
|
title = uiState.title.ifEmpty { stringResource(R.string.bookshelf) },
|
||||||
.takeIf { it.isNotEmpty() }
|
subtitle = uiState.subtitle,
|
||||||
?.let(onNavigateToSearch)
|
state = uiState,
|
||||||
},
|
scrollBehavior = scrollBehavior,
|
||||||
searchTrailingIcon = {
|
onBackClick = if (isEditMode) exitEditMode else null,
|
||||||
if (uiState.searchKey.isNotEmpty()) {
|
backNavigationIcon = AppIcons.Close,
|
||||||
TopBarActionButton(
|
showSearchAction = true,
|
||||||
onClick = { viewModel.setSearchKey("") },
|
onSearchToggle = { active ->
|
||||||
imageVector = AppIcons.Close,
|
if (BookshelfConfig.bookshelfSearchActionDirectToSearch) {
|
||||||
contentDescription = stringResource(R.string.clear)
|
onNavigateToSearch(uiState.searchKey.trim())
|
||||||
)
|
} else {
|
||||||
}
|
viewModel.setSearchMode(active)
|
||||||
},
|
if (!active && uiState.selectedGroupId != currentTabGroupId) {
|
||||||
topBarActions = {
|
viewModel.changeGroup(currentTabGroupId)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
},
|
||||||
secondaryActions = listOf(
|
onSearchQueryChange = { viewModel.setSearchKey(it) },
|
||||||
ActionItem(
|
onSearchSubmit = { rawQuery ->
|
||||||
text = stringResource(R.string.move_to_group),
|
rawQuery.trim()
|
||||||
icon = { Icon(Icons.Default.Bookmarks, contentDescription = null) },
|
.takeIf { it.isNotEmpty() }
|
||||||
onClick = {
|
?.let(onNavigateToSearch)
|
||||||
if (selectedBookUrls.isNotEmpty()) {
|
},
|
||||||
viewModel.showOverlay(BookshelfOverlay.GroupSelectSheet)
|
searchTrailingIcon = {
|
||||||
}
|
if (uiState.searchKey.isNotEmpty()) {
|
||||||
}
|
TopBarActionButton(
|
||||||
)
|
onClick = { viewModel.setSearchKey("") },
|
||||||
),
|
imageVector = AppIcons.Close,
|
||||||
onClearSelection = { viewModel.clearSelection() },
|
contentDescription = stringResource(R.string.clear)
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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) {
|
AppTabRow(
|
||||||
Box(modifier = Modifier) {
|
tabTitles = tabTitles,
|
||||||
SmallOutlinedIconToggleButton(
|
selectedTabIndex = selectedTabIndex,
|
||||||
checked = showGroupMenu,
|
onTabSelected = { index ->
|
||||||
onCheckedChange = {
|
scope.launch { pagerState.animateScrollToPage(index) }
|
||||||
if (it) {
|
|
||||||
viewModel.showOverlay(BookshelfOverlay.GroupMenu)
|
|
||||||
} else {
|
|
||||||
viewModel.dismissOverlay()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
imageVector = Icons.AutoMirrored.Filled.FormatListBulleted,
|
modifier = Modifier.weight(1f)
|
||||||
contentDescription = stringResource(R.string.group_manage)
|
|
||||||
)
|
)
|
||||||
RoundDropdownMenu(
|
|
||||||
expanded = showGroupMenu,
|
if (BookshelfConfig.shouldShowExpandButton) {
|
||||||
onDismissRequest = { viewModel.dismissOverlay() }
|
Box(modifier = Modifier) {
|
||||||
) { dismiss ->
|
SmallOutlinedIconToggleButton(
|
||||||
uiState.groups.forEachIndexed { index, group ->
|
checked = showGroupMenu,
|
||||||
RoundDropdownMenuItem(
|
onCheckedChange = {
|
||||||
text = group.groupName,
|
if (it) {
|
||||||
onClick = {
|
viewModel.showOverlay(BookshelfOverlay.GroupMenu)
|
||||||
if (uiState.isSearch) {
|
|
||||||
viewModel.changeGroup(group.groupId)
|
|
||||||
}
|
|
||||||
scope.launch { pagerState.animateScrollToPage(index) }
|
|
||||||
dismiss()
|
|
||||||
},
|
|
||||||
trailingIcon = {
|
|
||||||
val isSelected = if (uiState.isSearch) {
|
|
||||||
uiState.selectedGroupId == group.groupId
|
|
||||||
} else {
|
} else {
|
||||||
selectedTabIndex == index
|
viewModel.dismissOverlay()
|
||||||
}
|
}
|
||||||
if (isSelected) {
|
},
|
||||||
Icon(
|
imageVector = Icons.AutoMirrored.Filled.FormatListBulleted,
|
||||||
Icons.Default.Check,
|
contentDescription = stringResource(R.string.group_manage)
|
||||||
null,
|
|
||||||
modifier = Modifier.size(18.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
RoundDropdownMenu(
|
||||||
|
expanded = showGroupMenu,
|
||||||
if (uiState.isSearch) {
|
onDismissRequest = { viewModel.dismissOverlay() }
|
||||||
val allGroup = uiState.allGroups.firstOrNull {
|
) { dismiss ->
|
||||||
it.groupId == BookGroup.IdAll
|
uiState.groups.forEachIndexed { index, group ->
|
||||||
}
|
|
||||||
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(
|
RoundDropdownMenuItem(
|
||||||
text = group.groupName,
|
text = group.groupName,
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.changeGroup(group.groupId)
|
if (uiState.isSearch) {
|
||||||
|
viewModel.changeGroup(group.groupId)
|
||||||
|
}
|
||||||
|
scope.launch { pagerState.animateScrollToPage(index) }
|
||||||
dismiss()
|
dismiss()
|
||||||
},
|
},
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
if (uiState.selectedGroupId == group.groupId) {
|
val isSelected = if (uiState.isSearch) {
|
||||||
|
uiState.selectedGroupId == group.groupId
|
||||||
|
} else {
|
||||||
|
selectedTabIndex == index
|
||||||
|
}
|
||||||
|
if (isSelected) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Check,
|
Icons.Default.Check,
|
||||||
null,
|
null,
|
||||||
@@ -605,23 +586,57 @@ fun BookshelfScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
hiddenGroups.forEach { group ->
|
if (uiState.isSearch) {
|
||||||
RoundDropdownMenuItem(
|
val allGroup = uiState.allGroups.firstOrNull {
|
||||||
text = group.groupName,
|
it.groupId == BookGroup.IdAll
|
||||||
onClick = {
|
}
|
||||||
viewModel.changeGroup(group.groupId)
|
val hiddenGroups = uiState.allGroups.filter {
|
||||||
dismiss()
|
!it.show && it.groupId != BookGroup.IdAll
|
||||||
},
|
}
|
||||||
trailingIcon = {
|
|
||||||
if (uiState.selectedGroupId == group.groupId) {
|
if (allGroup != null || hiddenGroups.isNotEmpty()) {
|
||||||
Icon(
|
PillHeaderDivider(
|
||||||
Icons.Default.Check,
|
title = "${stringResource(R.string.all)} / ${stringResource(R.string.hide)}"
|
||||||
null,
|
)
|
||||||
modifier = Modifier.size(18.dp)
|
|
||||||
)
|
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 ->
|
) { paddingValues ->
|
||||||
val pullToRefreshState = rememberPullToRefreshState()
|
val pullToRefreshState = rememberPullToRefreshState()
|
||||||
val currentGroup by remember {
|
val currentGroup by remember {
|
||||||
@@ -934,6 +949,41 @@ fun BookshelfScreen(
|
|||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
.padding(top = paddingValues.calculateTopPadding())
|
.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(
|
GroupSelectSheet(
|
||||||
show = activeOverlay == BookshelfOverlay.GroupSelectSheet,
|
show = activeOverlay == BookshelfOverlay.GroupSelectSheet,
|
||||||
groups = groups,
|
groups = groups.filter { it.groupId > 0 },
|
||||||
currentGroupId = 0L,
|
currentGroupId = 0L,
|
||||||
onDismissRequest = { viewModel.dismissOverlay() },
|
onDismissRequest = { viewModel.dismissOverlay() },
|
||||||
onConfirm = { groupId ->
|
onConfirm = { groupId ->
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.ui.about.AboutActivity
|
import io.legado.app.ui.about.AboutActivity
|
||||||
import io.legado.app.ui.book.bookmark.AllBookmarkActivity
|
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.source.manage.BookSourceActivity
|
||||||
import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
|
import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
|
||||||
import io.legado.app.ui.dict.rule.DictRuleActivity
|
import io.legado.app.ui.dict.rule.DictRuleActivity
|
||||||
@@ -186,7 +185,7 @@ fun MyScreen(
|
|||||||
title = stringResource(R.string.read_record),
|
title = stringResource(R.string.read_record),
|
||||||
imageVector = Icons.Default.History,
|
imageVector = Icons.Default.History,
|
||||||
onClick = {
|
onClick = {
|
||||||
onNavigate(PrefClickEvent.StartActivity(ReadRecordActivity::class.java))
|
onNavigate(PrefClickEvent.OpenReadRecord)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
ClickableSettingItem(
|
ClickableSettingItem(
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ sealed class PrefClickEvent {
|
|||||||
data class CopyUrl(val url: String) : PrefClickEvent()
|
data class CopyUrl(val url: String) : PrefClickEvent()
|
||||||
data class ShowMd(val title: String, val path: String) : PrefClickEvent()
|
data class ShowMd(val title: String, val path: String) : PrefClickEvent()
|
||||||
data class StartActivity(val destination: Class<*>, val configTag: String? = null) : PrefClickEvent()
|
data class StartActivity(val destination: Class<*>, val configTag: String? = null) : PrefClickEvent()
|
||||||
|
object OpenReadRecord : PrefClickEvent()
|
||||||
object OpenBookCacheManage : PrefClickEvent()
|
object OpenBookCacheManage : PrefClickEvent()
|
||||||
object ToggleWebService : PrefClickEvent()
|
object ToggleWebService : PrefClickEvent()
|
||||||
object ExitApp : PrefClickEvent()
|
object ExitApp : PrefClickEvent()
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ private fun BaseCard(
|
|||||||
alpha: Float = 1f,
|
alpha: Float = 1f,
|
||||||
content: @Composable ColumnScope.() -> Unit
|
content: @Composable ColumnScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
val resolvedContainerColor = (containerColor ?: LegadoTheme.colorScheme.secondaryContainer)
|
val resolvedContainerColor = (containerColor ?: LegadoTheme.colorScheme.surfaceContainer)
|
||||||
.let { it.copy(alpha = it.alpha * alpha) }
|
.let { it.copy(alpha = it.alpha * alpha) }
|
||||||
val isTransparent = containerColor == Color.Transparent
|
val isTransparent = containerColor == Color.Transparent
|
||||||
val shape = RoundedCornerShape(cornerRadius)
|
val shape = RoundedCornerShape(cornerRadius)
|
||||||
|
|||||||
+2
-1
@@ -262,6 +262,7 @@ fun CompactClickableSettingItem(
|
|||||||
imageVector: ImageVector? = null,
|
imageVector: ImageVector? = null,
|
||||||
color: Color? = MaterialTheme.colorScheme.surface,
|
color: Color? = MaterialTheme.colorScheme.surface,
|
||||||
shape: Shape = MaterialTheme.shapes.small,
|
shape: Shape = MaterialTheme.shapes.small,
|
||||||
|
trailingContent: (@Composable () -> Unit)? = null,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
if (ThemeResolver.isMiuixEngine(composeEngine)) {
|
if (ThemeResolver.isMiuixEngine(composeEngine)) {
|
||||||
@@ -278,7 +279,7 @@ fun CompactClickableSettingItem(
|
|||||||
imageVector = imageVector,
|
imageVector = imageVector,
|
||||||
color = color,
|
color = color,
|
||||||
shape = shape,
|
shape = shape,
|
||||||
trailingContent = {
|
trailingContent = trailingContent ?: {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ChevronRight,
|
imageVector = Icons.Default.ChevronRight,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
|
|||||||
@@ -774,7 +774,12 @@
|
|||||||
<string name="auto_page_speed">自动翻页速度</string>
|
<string name="auto_page_speed">自动翻页速度</string>
|
||||||
<string name="sort_by_url">地址排序</string>
|
<string name="sort_by_url">地址排序</string>
|
||||||
<string name="backup_summary">本地和 WebDav 一起备份</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="import_old_summary">选择旧版备份文件夹</string>
|
||||||
<string name="enabled">已启用</string>
|
<string name="enabled">已启用</string>
|
||||||
<string name="disabled">已禁用</string>
|
<string name="disabled">已禁用</string>
|
||||||
|
|||||||
@@ -737,7 +737,12 @@
|
|||||||
<string name="other">其它</string>
|
<string name="other">其它</string>
|
||||||
<string name="official_account">legado-top</string>
|
<string name="official_account">legado-top</string>
|
||||||
<string name="backup_summary">本地和WebDav壹起備份</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="import_old_summary">選擇舊版備份文件夾</string>
|
||||||
<string name="enabled">已啓用</string>
|
<string name="enabled">已啓用</string>
|
||||||
<string name="disabled">已禁用</string>
|
<string name="disabled">已禁用</string>
|
||||||
|
|||||||
@@ -746,7 +746,12 @@
|
|||||||
<string name="auto_page_speed">自動翻頁速度</string>
|
<string name="auto_page_speed">自動翻頁速度</string>
|
||||||
<string name="sort_by_url">地址排序</string>
|
<string name="sort_by_url">地址排序</string>
|
||||||
<string name="backup_summary">本機和 WebDav 一起備份</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="import_old_summary">選擇舊版備份資料夾</string>
|
||||||
<string name="enabled">已啟用</string>
|
<string name="enabled">已啟用</string>
|
||||||
<string name="disabled">已禁用</string>
|
<string name="disabled">已禁用</string>
|
||||||
|
|||||||
@@ -141,4 +141,10 @@
|
|||||||
<item>High</item>
|
<item>High</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="backup_sync_mode_value">
|
||||||
|
<item>both</item>
|
||||||
|
<item>local</item>
|
||||||
|
<item>webdav</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -324,5 +324,11 @@
|
|||||||
<item>Maximum</item>
|
<item>Maximum</item>
|
||||||
</string-array>
|
</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>
|
<string name="all_version">All Version</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -804,7 +804,12 @@
|
|||||||
<string name="auto_page_speed">Auto scroll speed</string>
|
<string name="auto_page_speed">Auto scroll speed</string>
|
||||||
<string name="sort_by_url">Sort by URL</string>
|
<string name="sort_by_url">Sort by URL</string>
|
||||||
<string name="backup_summary">Backup the local and WebDAV simultaneously</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="import_old_summary">Select a legacy backup folder</string>
|
||||||
<string name="enabled">Enabled</string>
|
<string name="enabled">Enabled</string>
|
||||||
<string name="disabled">Disabled</string>
|
<string name="disabled">Disabled</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user