fix(read): 修复阅读菜单与高亮规则功能
This commit is contained in:
@@ -181,6 +181,7 @@ data class HighlightRuleConfigUiState(
|
|||||||
val editingRule: HighlightRule? = null,
|
val editingRule: HighlightRule? = null,
|
||||||
val showNewRule: Boolean = false,
|
val showNewRule: Boolean = false,
|
||||||
val deleteRule: HighlightRule? = null,
|
val deleteRule: HighlightRule? = null,
|
||||||
|
val importState: BaseImportUiState<HighlightRule> = BaseImportUiState.Idle,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
@@ -429,6 +430,21 @@ sealed interface ReadBookIntent {
|
|||||||
data class RequestDeleteHighlightRule(val rule: HighlightRule) : ReadBookIntent
|
data class RequestDeleteHighlightRule(val rule: HighlightRule) : ReadBookIntent
|
||||||
data object ConfirmDeleteHighlightRule : ReadBookIntent
|
data object ConfirmDeleteHighlightRule : ReadBookIntent
|
||||||
data object DismissDeleteHighlightRule : 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
|
// Icon picker — file IO handled by ViewModel
|
||||||
data class SaveMenuCustomIcon(val id: String, val uri: Uri) : ReadBookIntent
|
data class SaveMenuCustomIcon(val id: String, val uri: Uri) : ReadBookIntent
|
||||||
@@ -645,6 +661,8 @@ sealed interface ReadBookEffect {
|
|||||||
data object OpenHttpTtsImportPicker : ReadBookEffect
|
data object OpenHttpTtsImportPicker : ReadBookEffect
|
||||||
data object OpenHttpTtsExportPicker : ReadBookEffect
|
data object OpenHttpTtsExportPicker : ReadBookEffect
|
||||||
data class OpenHttpTtsLogin(val engineId: Long) : ReadBookEffect
|
data class OpenHttpTtsLogin(val engineId: Long) : ReadBookEffect
|
||||||
|
data object OpenHighlightRuleImportPicker : ReadBookEffect
|
||||||
|
data object OpenHighlightRuleExportPicker : ReadBookEffect
|
||||||
|
|
||||||
// Day/night toggle
|
// Day/night toggle
|
||||||
data object ToggleDayNight : ReadBookEffect
|
data object ToggleDayNight : ReadBookEffect
|
||||||
@@ -1128,6 +1146,9 @@ sealed interface ConfigUpdate {
|
|||||||
data class ReadBodyToLh(val value: Boolean) : ConfigUpdate {
|
data class ReadBodyToLh(val value: Boolean) : ConfigUpdate {
|
||||||
override val actions = setOf(ConfigUpdateAction.ReloadContent)
|
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 {
|
data class TextFullJustify(val value: Boolean) : ConfigUpdate {
|
||||||
override val actions = setOf(ConfigUpdateAction.ReloadContent)
|
override val actions = setOf(ConfigUpdateAction.ReloadContent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,6 +177,16 @@ class ReadBookController(
|
|||||||
newRefs.readView.upBattery(activity.sysBattery)
|
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() {
|
fun onRouteInitialized() {
|
||||||
applyReadBrightness()
|
applyReadBrightness()
|
||||||
upScreenTimeOut()
|
upScreenTimeOut()
|
||||||
@@ -261,7 +271,10 @@ class ReadBookController(
|
|||||||
ReadBookIntent.OpenReadMenuRoute(ReadBookMenuRoute.ReadAloud)
|
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)
|
state.isShowingSearchResult -> viewModel.onIntent(ReadBookIntent.ShowSearchMenu)
|
||||||
else -> viewModel.onIntent(ReadBookIntent.ShowMenu)
|
else -> viewModel.onIntent(ReadBookIntent.ShowMenu)
|
||||||
}
|
}
|
||||||
@@ -833,6 +846,8 @@ class ReadBookController(
|
|||||||
is ReadBookEffect.OpenHttpTtsImportPicker,
|
is ReadBookEffect.OpenHttpTtsImportPicker,
|
||||||
is ReadBookEffect.OpenHttpTtsExportPicker,
|
is ReadBookEffect.OpenHttpTtsExportPicker,
|
||||||
is ReadBookEffect.OpenHttpTtsLogin,
|
is ReadBookEffect.OpenHttpTtsLogin,
|
||||||
|
is ReadBookEffect.OpenHighlightRuleImportPicker,
|
||||||
|
is ReadBookEffect.OpenHighlightRuleExportPicker,
|
||||||
is ReadBookEffect.TtsCacheCleared,
|
is ReadBookEffect.TtsCacheCleared,
|
||||||
is ReadBookEffect.ExportJson,
|
is ReadBookEffect.ExportJson,
|
||||||
// DB query + bookmark effects — handled by ViewModel, ignored here
|
// DB query + bookmark effects — handled by ViewModel, ignored here
|
||||||
@@ -908,6 +923,9 @@ class ReadBookController(
|
|||||||
stopAutoPage()
|
stopAutoPage()
|
||||||
} else {
|
} else {
|
||||||
refs?.readView?.autoPager?.start()
|
refs?.readView?.autoPager?.start()
|
||||||
|
if (viewModel.uiState.value.menuVisible) {
|
||||||
|
refs?.readView?.autoPager?.pause()
|
||||||
|
}
|
||||||
viewModel.setAutoPage(true)
|
viewModel.setAutoPage(true)
|
||||||
onScreenOffTimerStart?.invoke()
|
onScreenOffTimerStart?.invoke()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,6 +122,10 @@ fun ReadBookRouteScreen(
|
|||||||
state.menuConfig.readMenuBottomBarBlurMode == ReadMenuBlurMode.LiquidGlass
|
state.menuConfig.readMenuBottomBarBlurMode == ReadMenuBlurMode.LiquidGlass
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(state.menuVisible) {
|
||||||
|
controller.onMenuVisibilityChanged(state.menuVisible)
|
||||||
|
}
|
||||||
|
|
||||||
// ── ActivityResult Launchers ──────────────────────────────────────
|
// ── ActivityResult Launchers ──────────────────────────────────────
|
||||||
|
|
||||||
val tocLauncher = rememberLauncherForActivityResult(TocActivityResult()) { result ->
|
val tocLauncher = rememberLauncherForActivityResult(TocActivityResult()) { result ->
|
||||||
@@ -235,6 +239,18 @@ fun ReadBookRouteScreen(
|
|||||||
uri?.let { viewModel.onIntent(ReadBookIntent.ExportHttpTtsToFile(it)) }
|
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(
|
val bookInfoLauncher = rememberLauncherForActivityResult(
|
||||||
StartActivityContract(BookInfoActivity::class.java)
|
StartActivityContract(BookInfoActivity::class.java)
|
||||||
) { result ->
|
) { result ->
|
||||||
@@ -408,6 +424,19 @@ fun ReadBookRouteScreen(
|
|||||||
exportHttpTtsPicker.launch("httpTTS.json")
|
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)
|
// All other effects — delegate to bridge (View/Window/Activity operations)
|
||||||
else -> controller.handleEffect(effect)
|
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.ImageSaveUtils
|
||||||
import io.legado.app.utils.NetworkUtils
|
import io.legado.app.utils.NetworkUtils
|
||||||
import io.legado.app.utils.StringUtils
|
import io.legado.app.utils.StringUtils
|
||||||
|
import io.legado.app.utils.fromJsonArray
|
||||||
import io.legado.app.utils.fromJsonObject
|
import io.legado.app.utils.fromJsonObject
|
||||||
import io.legado.app.utils.hexString
|
import io.legado.app.utils.hexString
|
||||||
import io.legado.app.utils.isAbsUrl
|
import io.legado.app.utils.isAbsUrl
|
||||||
@@ -410,6 +411,7 @@ class ReadBookViewModel(
|
|||||||
editingRule = null,
|
editingRule = null,
|
||||||
showNewRule = false,
|
showNewRule = false,
|
||||||
deleteRule = null,
|
deleteRule = null,
|
||||||
|
importState = BaseImportUiState.Idle,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -658,6 +660,36 @@ class ReadBookViewModel(
|
|||||||
is ReadBookIntent.DismissDeleteHighlightRule -> _uiState.update {
|
is ReadBookIntent.DismissDeleteHighlightRule -> _uiState.update {
|
||||||
it.copy(highlightRuleConfig = it.highlightRuleConfig.copy(deleteRule = null))
|
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.SaveMenuCustomIcon -> saveMenuCustomIcon(intent.id, intent.uri)
|
||||||
is ReadBookIntent.SaveTitleBarCustomIcon -> saveTitleBarCustomIcon(intent.id, intent.uri)
|
is ReadBookIntent.SaveTitleBarCustomIcon -> saveTitleBarCustomIcon(intent.id, intent.uri)
|
||||||
is ReadBookIntent.OpenMenuCustomIconPicker -> {
|
is ReadBookIntent.OpenMenuCustomIconPicker -> {
|
||||||
@@ -3306,6 +3338,12 @@ class ReadBookViewModel(
|
|||||||
readSettingsRepository.setReadBodyToLh(update.value)
|
readSettingsRepository.setReadBodyToLh(update.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is ConfigUpdate.DefaultSourceChangeAll -> {
|
||||||
|
ReadConfig.defaultSourceChangeAll = update.value
|
||||||
|
viewModelScope.launch {
|
||||||
|
readSettingsRepository.setDefaultSourceChangeAll(update.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
is ConfigUpdate.TextFullJustify -> {
|
is ConfigUpdate.TextFullJustify -> {
|
||||||
ReadBookConfig.textFullJustify = update.value
|
ReadBookConfig.textFullJustify = update.value
|
||||||
viewModelScope.launch {
|
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() {
|
private fun loadHighlightRules() {
|
||||||
val configName = ReadBookConfig.durConfig.name
|
val configName = ReadBookConfig.durConfig.name
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
@@ -3535,6 +3790,7 @@ class ReadBookViewModel(
|
|||||||
editingRule = null,
|
editingRule = null,
|
||||||
showNewRule = false,
|
showNewRule = false,
|
||||||
deleteRule = null,
|
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.ChapterProvider
|
||||||
import io.legado.app.ui.book.read.page.provider.TextPageFactory
|
import io.legado.app.ui.book.read.page.provider.TextPageFactory
|
||||||
import io.legado.app.ui.config.readConfig.ReadConfig
|
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.ui.widget.dialog.PhotoDialog
|
||||||
import io.legado.app.utils.activity
|
import io.legado.app.utils.activity
|
||||||
import io.legado.app.utils.dpToPx
|
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) {
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||||
super.onSizeChanged(w, h, oldw, oldh)
|
super.onSizeChanged(w, h, oldw, oldh)
|
||||||
if (!isMainView) return
|
if (!isMainView || MainNavigator.backNavigationInProgress) return
|
||||||
ChapterProvider.upViewSize(w, h)
|
ChapterProvider.upViewSize(w, h)
|
||||||
textPage.format()
|
textPage.format()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,33 @@
|
|||||||
package io.legado.app.ui.book.read.sheet
|
package io.legado.app.ui.book.read.sheet
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
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.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material.icons.filled.Edit
|
import androidx.compose.material.icons.filled.Edit
|
||||||
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.runtime.Composable
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.data.entities.HighlightRule
|
import io.legado.app.data.entities.HighlightRule
|
||||||
import io.legado.app.data.repository.configNames
|
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.TinySwitch
|
||||||
import io.legado.app.ui.widget.components.alert.AppAlertDialog
|
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.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.modalBottomSheet.AppModalBottomSheet
|
||||||
import io.legado.app.ui.widget.components.settingItem.TinySettingItem
|
import io.legado.app.ui.widget.components.settingItem.TinySettingItem
|
||||||
|
import sh.calvin.reorderable.ReorderableItem
|
||||||
|
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HighlightRuleConfigSheet(
|
fun HighlightRuleConfigSheet(
|
||||||
@@ -35,16 +53,105 @@ fun HighlightRuleConfigSheet(
|
|||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onIntent: (ReadBookIntent) -> 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(
|
AppModalBottomSheet(
|
||||||
show = show,
|
show = show,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
title = stringResource(R.string.highlight_rule_config),
|
title = stringResource(R.string.highlight_rule_config),
|
||||||
endAction = {
|
startAction = {
|
||||||
SmallTonalButton(
|
SmallTonalButton(
|
||||||
onClick = { onIntent(ReadBookIntent.AddHighlightRule) },
|
onClick = { onIntent(ReadBookIntent.AddHighlightRule) },
|
||||||
icon = Icons.Default.Add
|
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(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -52,26 +159,34 @@ fun HighlightRuleConfigSheet(
|
|||||||
.padding(bottom = 16.dp),
|
.padding(bottom = 16.dp),
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier = Modifier.weight(1f, fill = false),
|
modifier = Modifier.weight(1f, fill = false),
|
||||||
) {
|
) {
|
||||||
itemsIndexed(state.rules, key = { _, rule -> rule.id }) { _, rule ->
|
items(state.rules, key = { it.id }) { rule ->
|
||||||
HighlightRuleItem(
|
ReorderableItem(reorderableState, key = rule.id) { isDragging ->
|
||||||
rule = rule,
|
HighlightRuleItem(
|
||||||
onToggle = { enabled ->
|
rule = rule,
|
||||||
onIntent(ReadBookIntent.ToggleHighlightRule(rule, enabled))
|
onToggle = { enabled ->
|
||||||
},
|
onIntent(ReadBookIntent.ToggleHighlightRule(rule, enabled))
|
||||||
onEditClick = { onIntent(ReadBookIntent.EditHighlightRule(rule)) },
|
},
|
||||||
onDeleteClick = {
|
onEditClick = {
|
||||||
onIntent(ReadBookIntent.RequestDeleteHighlightRule(rule))
|
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
|
val editingRuleValue = state.editingRule
|
||||||
|
|
||||||
HighlightRuleEditSheet(
|
HighlightRuleEditSheet(
|
||||||
@@ -83,7 +198,6 @@ fun HighlightRuleConfigSheet(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add new rule
|
|
||||||
HighlightRuleEditSheet(
|
HighlightRuleEditSheet(
|
||||||
show = show && state.showNewRule,
|
show = show && state.showNewRule,
|
||||||
rule = null,
|
rule = null,
|
||||||
@@ -93,7 +207,6 @@ fun HighlightRuleConfigSheet(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Delete confirmation
|
|
||||||
val deletingRule = state.deleteRule
|
val deletingRule = state.deleteRule
|
||||||
val sureDeleteText = stringResource(R.string.sure_delete)
|
val sureDeleteText = stringResource(R.string.sure_delete)
|
||||||
AppAlertDialog(
|
AppAlertDialog(
|
||||||
@@ -106,7 +219,6 @@ fun HighlightRuleConfigSheet(
|
|||||||
dismissText = stringResource(android.R.string.cancel),
|
dismissText = stringResource(android.R.string.cancel),
|
||||||
onDismiss = { onIntent(ReadBookIntent.DismissDeleteHighlightRule) },
|
onDismiss = { onIntent(ReadBookIntent.DismissDeleteHighlightRule) },
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -115,12 +227,14 @@ private fun HighlightRuleItem(
|
|||||||
onToggle: (Boolean) -> Unit,
|
onToggle: (Boolean) -> Unit,
|
||||||
onEditClick: () -> Unit,
|
onEditClick: () -> Unit,
|
||||||
onDeleteClick: () -> Unit,
|
onDeleteClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val configLabel = rule.configName?.configNames()?.joinToString("、") ?: "全局"
|
val configLabel = rule.configName?.configNames()?.joinToString("、") ?: "全局"
|
||||||
TinySettingItem(
|
TinySettingItem(
|
||||||
title = rule.name.ifBlank { rule.displayPattern() },
|
title = rule.name.ifBlank { rule.displayPattern() },
|
||||||
description = "${rule.styleSummary()} · $configLabel",
|
description = "${rule.styleSummary()} · $configLabel",
|
||||||
onClick = onEditClick,
|
onClick = onEditClick,
|
||||||
|
modifier = modifier,
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
TinySwitch(
|
TinySwitch(
|
||||||
|
|||||||
@@ -125,6 +125,9 @@ fun MoreConfigSheet(
|
|||||||
onAutoChangeSourceChange = {
|
onAutoChangeSourceChange = {
|
||||||
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.AutoChangeSource(it)))
|
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.AutoChangeSource(it)))
|
||||||
},
|
},
|
||||||
|
onDefaultSourceChangeAllChange = {
|
||||||
|
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.DefaultSourceChangeAll(it)))
|
||||||
|
},
|
||||||
onAutoSuggestDayNightChange = {
|
onAutoSuggestDayNightChange = {
|
||||||
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.AutoSuggestDayNight(it)))
|
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.AutoSuggestDayNight(it)))
|
||||||
},
|
},
|
||||||
@@ -301,6 +304,7 @@ private fun OtherSettings(
|
|||||||
onSliderVibratorChange: (Boolean) -> Unit,
|
onSliderVibratorChange: (Boolean) -> Unit,
|
||||||
onSelectVibratorChange: (Boolean) -> Unit,
|
onSelectVibratorChange: (Boolean) -> Unit,
|
||||||
onAutoChangeSourceChange: (Boolean) -> Unit,
|
onAutoChangeSourceChange: (Boolean) -> Unit,
|
||||||
|
onDefaultSourceChangeAllChange: (Boolean) -> Unit,
|
||||||
onAutoSuggestDayNightChange: (Boolean) -> Unit,
|
onAutoSuggestDayNightChange: (Boolean) -> Unit,
|
||||||
onSelectTextChange: (Boolean) -> Unit,
|
onSelectTextChange: (Boolean) -> Unit,
|
||||||
onNoAnimScrollPageChange: (Boolean) -> Unit,
|
onNoAnimScrollPageChange: (Boolean) -> Unit,
|
||||||
@@ -333,6 +337,12 @@ private fun OtherSettings(
|
|||||||
checked = preferences.autoChangeSource,
|
checked = preferences.autoChangeSource,
|
||||||
onCheckedChange = onAutoChangeSourceChange,
|
onCheckedChange = onAutoChangeSourceChange,
|
||||||
)
|
)
|
||||||
|
TinySwitchSettingItem(
|
||||||
|
title = stringResource(R.string.read_change_all),
|
||||||
|
description = stringResource(R.string.read_change_all_s),
|
||||||
|
checked = preferences.defaultSourceChangeAll,
|
||||||
|
onCheckedChange = onDefaultSourceChangeAllChange,
|
||||||
|
)
|
||||||
TinySwitchSettingItem(
|
TinySwitchSettingItem(
|
||||||
title = stringResource(R.string.auto_switch_theme_reminder_title),
|
title = stringResource(R.string.auto_switch_theme_reminder_title),
|
||||||
description = stringResource(R.string.auto_switch_theme_reminder_desc),
|
description = stringResource(R.string.auto_switch_theme_reminder_desc),
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
object MainNavigator {
|
object MainNavigator {
|
||||||
|
|
||||||
private var backNavigationInProgress = false
|
var backNavigationInProgress = false
|
||||||
|
private set
|
||||||
private val navigationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
private val navigationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||||
private var backNavigationResetJob: Job? = null
|
private var backNavigationResetJob: Job? = null
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user