fix(read): 修复阅读菜单与高亮规则功能

This commit is contained in:
HapeLee
2026-07-01 23:43:14 +08:00
parent 6e4917fb5d
commit da00f712bf
8 changed files with 471 additions and 21 deletions
@@ -181,6 +181,7 @@ data class HighlightRuleConfigUiState(
val editingRule: HighlightRule? = null,
val showNewRule: Boolean = false,
val deleteRule: HighlightRule? = null,
val importState: BaseImportUiState<HighlightRule> = BaseImportUiState.Idle,
)
@Stable
@@ -429,6 +430,21 @@ sealed interface ReadBookIntent {
data class RequestDeleteHighlightRule(val rule: HighlightRule) : ReadBookIntent
data object ConfirmDeleteHighlightRule : ReadBookIntent
data object DismissDeleteHighlightRule : ReadBookIntent
data class MoveHighlightRule(val from: Int, val to: Int) : ReadBookIntent
data object SaveHighlightRuleOrder : ReadBookIntent
data class ImportHighlightRuleSource(val text: String) : ReadBookIntent
data object OpenHighlightRuleImportPicker : ReadBookIntent
data class HighlightRuleImportFileSelected(val uri: Uri) : ReadBookIntent
data object CancelHighlightRuleImport : ReadBookIntent
data class ToggleHighlightRuleImportSelection(val index: Int) : ReadBookIntent
data class ToggleHighlightRuleImportAll(val isSelected: Boolean) : ReadBookIntent
data class UpdateHighlightRuleImportItem(
val index: Int,
val rule: HighlightRule,
) : ReadBookIntent
data object SaveImportedHighlightRules : ReadBookIntent
data object ExportHighlightRules : ReadBookIntent
data class ExportHighlightRulesToFile(val uri: Uri) : ReadBookIntent
// Icon picker — file IO handled by ViewModel
data class SaveMenuCustomIcon(val id: String, val uri: Uri) : ReadBookIntent
@@ -645,6 +661,8 @@ sealed interface ReadBookEffect {
data object OpenHttpTtsImportPicker : ReadBookEffect
data object OpenHttpTtsExportPicker : ReadBookEffect
data class OpenHttpTtsLogin(val engineId: Long) : ReadBookEffect
data object OpenHighlightRuleImportPicker : ReadBookEffect
data object OpenHighlightRuleExportPicker : ReadBookEffect
// Day/night toggle
data object ToggleDayNight : ReadBookEffect
@@ -1128,6 +1146,9 @@ sealed interface ConfigUpdate {
data class ReadBodyToLh(val value: Boolean) : ConfigUpdate {
override val actions = setOf(ConfigUpdateAction.ReloadContent)
}
data class DefaultSourceChangeAll(val value: Boolean) : ConfigUpdate {
override val actions = emptySet<ConfigUpdateAction>()
}
data class TextFullJustify(val value: Boolean) : ConfigUpdate {
override val actions = setOf(ConfigUpdateAction.ReloadContent)
}
@@ -177,6 +177,16 @@ class ReadBookController(
newRefs.readView.upBattery(activity.sysBattery)
}
fun onMenuVisibilityChanged(visible: Boolean) {
val autoPager = refs?.readView?.autoPager ?: return
if (!autoPager.isRunning) return
if (visible) {
autoPager.pause()
} else {
autoPager.resume()
}
}
fun onRouteInitialized() {
applyReadBrightness()
upScreenTimeOut()
@@ -261,7 +271,10 @@ class ReadBookController(
ReadBookIntent.OpenReadMenuRoute(ReadBookMenuRoute.ReadAloud)
)
isAutoPage -> viewModel.onIntent(ReadBookIntent.OpenReadMenuRoute(ReadBookMenuRoute.AutoRead))
isAutoPage -> {
refs?.readView?.autoPager?.pause()
viewModel.onIntent(ReadBookIntent.OpenReadMenuRoute(ReadBookMenuRoute.AutoRead))
}
state.isShowingSearchResult -> viewModel.onIntent(ReadBookIntent.ShowSearchMenu)
else -> viewModel.onIntent(ReadBookIntent.ShowMenu)
}
@@ -833,6 +846,8 @@ class ReadBookController(
is ReadBookEffect.OpenHttpTtsImportPicker,
is ReadBookEffect.OpenHttpTtsExportPicker,
is ReadBookEffect.OpenHttpTtsLogin,
is ReadBookEffect.OpenHighlightRuleImportPicker,
is ReadBookEffect.OpenHighlightRuleExportPicker,
is ReadBookEffect.TtsCacheCleared,
is ReadBookEffect.ExportJson,
// DB query + bookmark effects — handled by ViewModel, ignored here
@@ -908,6 +923,9 @@ class ReadBookController(
stopAutoPage()
} else {
refs?.readView?.autoPager?.start()
if (viewModel.uiState.value.menuVisible) {
refs?.readView?.autoPager?.pause()
}
viewModel.setAutoPage(true)
onScreenOffTimerStart?.invoke()
}
@@ -122,6 +122,10 @@ fun ReadBookRouteScreen(
state.menuConfig.readMenuBottomBarBlurMode == ReadMenuBlurMode.LiquidGlass
)
LaunchedEffect(state.menuVisible) {
controller.onMenuVisibilityChanged(state.menuVisible)
}
// ── ActivityResult Launchers ──────────────────────────────────────
val tocLauncher = rememberLauncherForActivityResult(TocActivityResult()) { result ->
@@ -235,6 +239,18 @@ fun ReadBookRouteScreen(
uri?.let { viewModel.onIntent(ReadBookIntent.ExportHttpTtsToFile(it)) }
}
val importHighlightRulePicker = rememberLauncherForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri ->
uri?.let { viewModel.onIntent(ReadBookIntent.HighlightRuleImportFileSelected(it)) }
}
val exportHighlightRulePicker = rememberLauncherForActivityResult(
ActivityResultContracts.CreateDocument("application/json")
) { uri ->
uri?.let { viewModel.onIntent(ReadBookIntent.ExportHighlightRulesToFile(it)) }
}
val bookInfoLauncher = rememberLauncherForActivityResult(
StartActivityContract(BookInfoActivity::class.java)
) { result ->
@@ -408,6 +424,19 @@ fun ReadBookRouteScreen(
exportHttpTtsPicker.launch("httpTTS.json")
}
is ReadBookEffect.OpenHighlightRuleImportPicker -> {
importHighlightRulePicker.launch(
arrayOf(
"application/json",
"text/plain"
)
)
}
is ReadBookEffect.OpenHighlightRuleExportPicker -> {
exportHighlightRulePicker.launch("highlightRule.json")
}
// All other effects — delegate to bridge (View/Window/Activity operations)
else -> controller.handleEffect(effect)
}
@@ -83,6 +83,7 @@ import io.legado.app.utils.GSON
import io.legado.app.utils.ImageSaveUtils
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.StringUtils
import io.legado.app.utils.fromJsonArray
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.hexString
import io.legado.app.utils.isAbsUrl
@@ -410,6 +411,7 @@ class ReadBookViewModel(
editingRule = null,
showNewRule = false,
deleteRule = null,
importState = BaseImportUiState.Idle,
),
)
} else {
@@ -658,6 +660,36 @@ class ReadBookViewModel(
is ReadBookIntent.DismissDeleteHighlightRule -> _uiState.update {
it.copy(highlightRuleConfig = it.highlightRuleConfig.copy(deleteRule = null))
}
is ReadBookIntent.MoveHighlightRule -> moveHighlightRule(intent.from, intent.to)
is ReadBookIntent.SaveHighlightRuleOrder -> {
saveHighlightRules(_uiState.value.highlightRuleConfig.rules)
}
is ReadBookIntent.ImportHighlightRuleSource -> {
importHighlightRuleSource(intent.text)
}
is ReadBookIntent.OpenHighlightRuleImportPicker -> {
_effects.tryEmit(ReadBookEffect.OpenHighlightRuleImportPicker)
}
is ReadBookIntent.HighlightRuleImportFileSelected -> {
importHighlightRuleFile(intent.uri)
}
is ReadBookIntent.CancelHighlightRuleImport -> cancelHighlightRuleImport()
is ReadBookIntent.ToggleHighlightRuleImportSelection -> {
toggleHighlightRuleImportSelection(intent.index)
}
is ReadBookIntent.ToggleHighlightRuleImportAll -> {
toggleHighlightRuleImportAll(intent.isSelected)
}
is ReadBookIntent.UpdateHighlightRuleImportItem -> {
updateHighlightRuleImportItem(intent.index, intent.rule)
}
is ReadBookIntent.SaveImportedHighlightRules -> saveImportedHighlightRules()
is ReadBookIntent.ExportHighlightRules -> {
_effects.tryEmit(ReadBookEffect.OpenHighlightRuleExportPicker)
}
is ReadBookIntent.ExportHighlightRulesToFile -> {
exportHighlightRules(intent.uri)
}
is ReadBookIntent.SaveMenuCustomIcon -> saveMenuCustomIcon(intent.id, intent.uri)
is ReadBookIntent.SaveTitleBarCustomIcon -> saveTitleBarCustomIcon(intent.id, intent.uri)
is ReadBookIntent.OpenMenuCustomIconPicker -> {
@@ -3306,6 +3338,12 @@ class ReadBookViewModel(
readSettingsRepository.setReadBodyToLh(update.value)
}
}
is ConfigUpdate.DefaultSourceChangeAll -> {
ReadConfig.defaultSourceChangeAll = update.value
viewModelScope.launch {
readSettingsRepository.setDefaultSourceChangeAll(update.value)
}
}
is ConfigUpdate.TextFullJustify -> {
ReadBookConfig.textFullJustify = update.value
viewModelScope.launch {
@@ -3526,6 +3564,223 @@ class ReadBookViewModel(
}
}
private fun moveHighlightRule(from: Int, to: Int) {
val rules = _uiState.value.highlightRuleConfig.rules
if (from !in rules.indices || to !in rules.indices) return
val reordered = rules.toMutableList().apply {
add(to, removeAt(from))
}
_uiState.update {
it.copy(
highlightRuleConfig = it.highlightRuleConfig.copy(
rules = reordered.toImmutableList()
)
)
}
}
private fun importHighlightRuleSource(text: String) {
_uiState.update {
it.copy(
highlightRuleConfig = it.highlightRuleConfig.copy(
importState = BaseImportUiState.Loading
)
)
}
execute {
val importedRules = importHighlightRuleSourceAwait(text.trim())
.map(highlightRuleRepository::sanitizeRule)
if (importedRules.isEmpty()) {
throw NoStackTraceException(context.getString(R.string.wrong_format))
}
val oldRules = highlightRuleRepository.load(ReadBookConfig.durConfig.name)
.associateBy { it.id }
BaseImportUiState.Success(
source = text,
items = importedRules.map { rule ->
val oldRule = oldRules[rule.id]
val status = when {
oldRule == null -> ImportStatus.New
oldRule != rule -> ImportStatus.Update
else -> ImportStatus.Existing
}
ImportItemWrapper(
data = rule,
oldData = oldRule,
status = status,
isSelected = status != ImportStatus.Existing,
)
}
)
}.onSuccess { importState ->
_uiState.update {
it.copy(
highlightRuleConfig = it.highlightRuleConfig.copy(
importState = importState
)
)
}
}.onError {
AppLog.put("导入高亮规则失败\n${it.localizedMessage}", it, true)
_uiState.update { state ->
state.copy(
highlightRuleConfig = state.highlightRuleConfig.copy(
importState = BaseImportUiState.Error(
it.localizedMessage ?: context.getString(R.string.wrong_format)
)
)
)
}
}
}
private suspend fun importHighlightRuleSourceAwait(text: String): List<HighlightRule> {
return when {
text.isJsonArray() -> GSON.fromJsonArray<HighlightRule>(text).getOrThrow()
text.isJsonObject() -> listOf(
GSON.fromJsonObject<HighlightRule>(text).getOrThrow()
)
text.isAbsUrl() -> {
val body = okHttpClient.newCallResponseBody {
if (text.endsWith("#requestWithoutUA")) {
url(text.substringBeforeLast("#requestWithoutUA"))
header(AppConst.UA_NAME, "null")
} else {
url(text)
}
}.decompressed().text()
importHighlightRuleSourceAwait(body)
}
else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
}
}
private fun importHighlightRuleFile(uri: Uri) {
execute<String?> {
context.contentResolver.openInputStream(uri)?.use {
it.reader().readText()
}
}.onSuccess { text ->
if (text.isNullOrBlank()) {
_uiState.update { state ->
state.copy(
highlightRuleConfig = state.highlightRuleConfig.copy(
importState = BaseImportUiState.Error(
context.getString(R.string.wrong_format)
)
)
)
}
} else {
importHighlightRuleSource(text)
}
}.onError {
_uiState.update { state ->
state.copy(
highlightRuleConfig = state.highlightRuleConfig.copy(
importState = BaseImportUiState.Error(
it.localizedMessage ?: context.getString(R.string.wrong_format)
)
)
)
}
}
}
private fun cancelHighlightRuleImport() {
_uiState.update {
it.copy(
highlightRuleConfig = it.highlightRuleConfig.copy(
importState = BaseImportUiState.Idle
)
)
}
}
private fun toggleHighlightRuleImportSelection(index: Int) {
val importState = _uiState.value.highlightRuleConfig.importState
as? BaseImportUiState.Success<HighlightRule> ?: return
if (index !in importState.items.indices) return
val items = importState.items.toMutableList()
val item = items[index]
items[index] = item.copy(isSelected = !item.isSelected)
_uiState.update {
it.copy(
highlightRuleConfig = it.highlightRuleConfig.copy(
importState = importState.copy(items = items)
)
)
}
}
private fun toggleHighlightRuleImportAll(isSelected: Boolean) {
val importState = _uiState.value.highlightRuleConfig.importState
as? BaseImportUiState.Success<HighlightRule> ?: return
_uiState.update {
it.copy(
highlightRuleConfig = it.highlightRuleConfig.copy(
importState = importState.copy(
items = importState.items.map { item ->
item.copy(isSelected = isSelected)
}
)
)
)
}
}
private fun updateHighlightRuleImportItem(index: Int, rule: HighlightRule) {
val importState = _uiState.value.highlightRuleConfig.importState
as? BaseImportUiState.Success<HighlightRule> ?: return
if (index !in importState.items.indices) return
val items = importState.items.toMutableList()
items[index] = items[index].copy(data = rule)
_uiState.update {
it.copy(
highlightRuleConfig = it.highlightRuleConfig.copy(
importState = importState.copy(
items = items,
version = importState.version + 1,
)
)
)
}
}
private fun saveImportedHighlightRules() {
val state = _uiState.value.highlightRuleConfig
val importState = state.importState
as? BaseImportUiState.Success<HighlightRule> ?: return
val importedRules = importState.items
.filter { it.isSelected }
.map { highlightRuleRepository.sanitizeRule(it.data) }
if (importedRules.isEmpty()) return
val importedById = importedRules.associateBy { it.id }
val mergedRules = state.rules.map { importedById[it.id] ?: it } +
importedRules.filter { imported -> state.rules.none { it.id == imported.id } }
saveHighlightRules(mergedRules)
cancelHighlightRuleImport()
}
private fun exportHighlightRules(uri: Uri) {
val rules = _uiState.value.highlightRuleConfig.rules
execute {
context.contentResolver.openOutputStream(uri)?.use { output ->
output.bufferedWriter().use { writer ->
writer.write(GSON.toJson(rules))
}
} ?: throw NoStackTraceException(context.getString(R.string.error))
}.onSuccess {
_effects.tryEmit(ReadBookEffect.ShowToast(context.getString(R.string.export_success)))
}.onError {
_effects.tryEmit(
ReadBookEffect.ShowToast(
it.localizedMessage ?: context.getString(R.string.error)
)
)
}
}
private fun loadHighlightRules() {
val configName = ReadBookConfig.durConfig.name
_uiState.update {
@@ -3535,6 +3790,7 @@ class ReadBookViewModel(
editingRule = null,
showNewRule = false,
deleteRule = null,
importState = BaseImportUiState.Idle,
),
)
}
@@ -25,6 +25,7 @@ import io.legado.app.ui.book.read.page.entities.column.TextHtmlColumn
import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.book.read.page.provider.TextPageFactory
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.ui.main.MainNavigator
import io.legado.app.ui.widget.dialog.PhotoDialog
import io.legado.app.utils.activity
import io.legado.app.utils.dpToPx
@@ -96,7 +97,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (!isMainView) return
if (!isMainView || MainNavigator.backNavigationInProgress) return
ChapterProvider.upViewSize(w, h)
textPage.format()
}
@@ -1,22 +1,33 @@
package io.legado.app.ui.book.read.sheet
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import io.legado.app.R
import io.legado.app.data.entities.HighlightRule
import io.legado.app.data.repository.configNames
@@ -25,8 +36,15 @@ import io.legado.app.ui.book.read.ReadBookIntent
import io.legado.app.ui.widget.components.TinySwitch
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
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.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.TinySettingItem
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState
@Composable
fun HighlightRuleConfigSheet(
@@ -35,16 +53,105 @@ fun HighlightRuleConfigSheet(
onDismissRequest: () -> Unit,
onIntent: (ReadBookIntent) -> Unit,
) {
var showImportSheet by remember { mutableStateOf(false) }
var showSourceInput by remember { mutableStateOf(false) }
val listState = rememberLazyListState()
val hapticFeedback = LocalHapticFeedback.current
var orderChanged by remember { mutableStateOf(false) }
val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
orderChanged = true
onIntent(ReadBookIntent.MoveHighlightRule(from.index, to.index))
hapticFeedback.performHapticFeedback(HapticFeedbackType.SegmentFrequentTick)
}
LaunchedEffect(reorderableState.isAnyItemDragging) {
if (!reorderableState.isAnyItemDragging && orderChanged) {
orderChanged = false
onIntent(ReadBookIntent.SaveHighlightRuleOrder)
}
}
SourceInputDialog(
show = showSourceInput,
title = stringResource(R.string.import_on_line),
onDismissRequest = { showSourceInput = false },
onConfirm = {
showSourceInput = false
onIntent(ReadBookIntent.ImportHighlightRuleSource(it))
},
)
FilePickerSheet(
show = showImportSheet,
onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.highlight_rule_config),
onSelectSysFile = {
showImportSheet = false
onIntent(ReadBookIntent.OpenHighlightRuleImportPicker)
},
onManualInput = {
showImportSheet = false
showSourceInput = true
},
allowExtensions = arrayOf("json", "txt"),
)
BatchImportDialog(
title = stringResource(R.string.highlight_rule_config),
importState = state.importState,
onDismissRequest = { onIntent(ReadBookIntent.CancelHighlightRuleImport) },
onToggleItem = {
onIntent(ReadBookIntent.ToggleHighlightRuleImportSelection(it))
},
onToggleAll = {
onIntent(ReadBookIntent.ToggleHighlightRuleImportAll(it))
},
onUpdateItem = { index, rule ->
onIntent(ReadBookIntent.UpdateHighlightRuleImportItem(index, rule))
},
onConfirm = {
onIntent(ReadBookIntent.SaveImportedHighlightRules)
},
itemTitle = { it.name.ifBlank { it.displayPattern() } },
itemSubtitle = { it.pattern.takeIf(String::isNotBlank) },
)
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest,
title = stringResource(R.string.highlight_rule_config),
endAction = {
startAction = {
SmallTonalButton(
onClick = { onIntent(ReadBookIntent.AddHighlightRule) },
icon = Icons.Default.Add
)
}
},
endAction = {
var expanded by remember { mutableStateOf(false) }
Box {
SmallTonalButton(
onClick = { expanded = true },
icon = Icons.Default.MoreVert
)
RoundDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
RoundDropdownMenuItem(
text = stringResource(R.string.import_action),
onClick = {
expanded = false
showImportSheet = true
},
)
RoundDropdownMenuItem(
text = stringResource(R.string.export),
onClick = {
expanded = false
onIntent(ReadBookIntent.ExportHighlightRules)
},
)
}
}
},
) {
Column(
modifier = Modifier
@@ -52,26 +159,34 @@ fun HighlightRuleConfigSheet(
.padding(bottom = 16.dp),
) {
LazyColumn(
state = listState,
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.weight(1f, fill = false),
) {
itemsIndexed(state.rules, key = { _, rule -> rule.id }) { _, rule ->
HighlightRuleItem(
rule = rule,
onToggle = { enabled ->
onIntent(ReadBookIntent.ToggleHighlightRule(rule, enabled))
},
onEditClick = { onIntent(ReadBookIntent.EditHighlightRule(rule)) },
onDeleteClick = {
onIntent(ReadBookIntent.RequestDeleteHighlightRule(rule))
},
)
items(state.rules, key = { it.id }) { rule ->
ReorderableItem(reorderableState, key = rule.id) { isDragging ->
HighlightRuleItem(
rule = rule,
onToggle = { enabled ->
onIntent(ReadBookIntent.ToggleHighlightRule(rule, enabled))
},
onEditClick = {
onIntent(ReadBookIntent.EditHighlightRule(rule))
},
onDeleteClick = {
onIntent(ReadBookIntent.RequestDeleteHighlightRule(rule))
},
modifier = Modifier
.longPressDraggableHandle()
.zIndex(if (isDragging) 1f else 0f)
.animateItem(),
)
}
}
}
}
}
// Edit existing rule
val editingRuleValue = state.editingRule
HighlightRuleEditSheet(
@@ -83,7 +198,6 @@ fun HighlightRuleConfigSheet(
},
)
// Add new rule
HighlightRuleEditSheet(
show = show && state.showNewRule,
rule = null,
@@ -93,7 +207,6 @@ fun HighlightRuleConfigSheet(
},
)
// Delete confirmation
val deletingRule = state.deleteRule
val sureDeleteText = stringResource(R.string.sure_delete)
AppAlertDialog(
@@ -106,7 +219,6 @@ fun HighlightRuleConfigSheet(
dismissText = stringResource(android.R.string.cancel),
onDismiss = { onIntent(ReadBookIntent.DismissDeleteHighlightRule) },
)
}
@Composable
@@ -115,12 +227,14 @@ private fun HighlightRuleItem(
onToggle: (Boolean) -> Unit,
onEditClick: () -> Unit,
onDeleteClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val configLabel = rule.configName?.configNames()?.joinToString("") ?: "全局"
TinySettingItem(
title = rule.name.ifBlank { rule.displayPattern() },
description = "${rule.styleSummary()} · $configLabel",
onClick = onEditClick,
modifier = modifier,
trailingContent = {
Row(verticalAlignment = Alignment.CenterVertically) {
TinySwitch(
@@ -125,6 +125,9 @@ fun MoreConfigSheet(
onAutoChangeSourceChange = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.AutoChangeSource(it)))
},
onDefaultSourceChangeAllChange = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.DefaultSourceChangeAll(it)))
},
onAutoSuggestDayNightChange = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.AutoSuggestDayNight(it)))
},
@@ -301,6 +304,7 @@ private fun OtherSettings(
onSliderVibratorChange: (Boolean) -> Unit,
onSelectVibratorChange: (Boolean) -> Unit,
onAutoChangeSourceChange: (Boolean) -> Unit,
onDefaultSourceChangeAllChange: (Boolean) -> Unit,
onAutoSuggestDayNightChange: (Boolean) -> Unit,
onSelectTextChange: (Boolean) -> Unit,
onNoAnimScrollPageChange: (Boolean) -> Unit,
@@ -333,6 +337,12 @@ private fun OtherSettings(
checked = preferences.autoChangeSource,
onCheckedChange = onAutoChangeSourceChange,
)
TinySwitchSettingItem(
title = stringResource(R.string.read_change_all),
description = stringResource(R.string.read_change_all_s),
checked = preferences.defaultSourceChangeAll,
onCheckedChange = onDefaultSourceChangeAllChange,
)
TinySwitchSettingItem(
title = stringResource(R.string.auto_switch_theme_reminder_title),
description = stringResource(R.string.auto_switch_theme_reminder_desc),
@@ -14,7 +14,8 @@ import kotlinx.coroutines.launch
object MainNavigator {
private var backNavigationInProgress = false
var backNavigationInProgress = false
private set
private val navigationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
private var backNavigationResetJob: Job? = null