refactor: 重构替换规则界面为MVI架构
This commit is contained in:
7 files changed
+379
-186
No files matched your search
@@ -19,7 +19,7 @@ import androidx.navigation3.runtime.entryProvider
|
|||||||
import androidx.navigation3.runtime.rememberNavBackStack
|
import androidx.navigation3.runtime.rememberNavBackStack
|
||||||
import androidx.navigation3.ui.NavDisplay
|
import androidx.navigation3.ui.NavDisplay
|
||||||
import io.legado.app.base.BaseComposeActivity
|
import io.legado.app.base.BaseComposeActivity
|
||||||
import io.legado.app.ui.replace.edit.ReplaceEditScreen
|
import io.legado.app.ui.replace.edit.ReplaceEditRouteScreen
|
||||||
import io.legado.app.ui.replace.edit.ReplaceEditViewModel
|
import io.legado.app.ui.replace.edit.ReplaceEditViewModel
|
||||||
import io.legado.app.ui.theme.AppTheme
|
import io.legado.app.ui.theme.AppTheme
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@@ -136,7 +136,7 @@ class ReplaceRuleActivity : BaseComposeActivity() {
|
|||||||
},
|
},
|
||||||
entryProvider = entryProvider {
|
entryProvider = entryProvider {
|
||||||
entry<ReplaceRuleRoute> {
|
entry<ReplaceRuleRoute> {
|
||||||
ReplaceRuleScreen(
|
ReplaceRuleRouteScreen(
|
||||||
onBackClick = { finish() },
|
onBackClick = { finish() },
|
||||||
onNavigateToEdit = { route -> backStack.add(route) }
|
onNavigateToEdit = { route -> backStack.add(route) }
|
||||||
)
|
)
|
||||||
@@ -147,7 +147,7 @@ class ReplaceRuleActivity : BaseComposeActivity() {
|
|||||||
key = "replace_edit_${System.identityHashCode(route)}"
|
key = "replace_edit_${System.identityHashCode(route)}"
|
||||||
) { parametersOf(route) }
|
) { parametersOf(route) }
|
||||||
|
|
||||||
ReplaceEditScreen(
|
ReplaceEditRouteScreen(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onBack = {
|
onBack = {
|
||||||
if (backStack.size > 1) {
|
if (backStack.size > 1) {
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package io.legado.app.ui.replace
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import io.legado.app.data.entities.ReplaceRule
|
||||||
|
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
|
||||||
|
import io.legado.app.ui.widget.components.list.InteractionState
|
||||||
|
import io.legado.app.ui.widget.components.list.ListUiState
|
||||||
|
import io.legado.app.ui.widget.components.list.SelectableItem
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class ReplaceRuleItemUi(
|
||||||
|
override val id: Long,
|
||||||
|
val name: String,
|
||||||
|
val isEnabled: Boolean,
|
||||||
|
val group: String?,
|
||||||
|
val rule: ReplaceRule
|
||||||
|
) : SelectableItem<Long>
|
||||||
|
|
||||||
|
data class ReplaceRuleUiState(
|
||||||
|
override val items: List<ReplaceRuleItemUi> = emptyList(),
|
||||||
|
override val selectedIds: Set<Long> = emptySet(),
|
||||||
|
override val searchKey: String = "",
|
||||||
|
val sortMode: String = "desc",
|
||||||
|
val interaction: InteractionState = InteractionState()
|
||||||
|
) : ListUiState<ReplaceRuleItemUi> {
|
||||||
|
override val isSearch: Boolean get() = interaction.isSearchMode
|
||||||
|
override val isLoading: Boolean get() = interaction.isUploading
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface ReplaceRuleIntent {
|
||||||
|
data class SetSearchMode(val active: Boolean) : ReplaceRuleIntent
|
||||||
|
data class UpdateSearchQuery(val query: String) : ReplaceRuleIntent
|
||||||
|
data object ClearSelection : ReplaceRuleIntent
|
||||||
|
data object SelectAll : ReplaceRuleIntent
|
||||||
|
data object InvertSelection : ReplaceRuleIntent
|
||||||
|
data class SetSelection(val ids: Set<Long>) : ReplaceRuleIntent
|
||||||
|
data class ToggleSelection(val id: Long) : ReplaceRuleIntent
|
||||||
|
data object EnableSelection : ReplaceRuleIntent
|
||||||
|
data object DisableSelection : ReplaceRuleIntent
|
||||||
|
data object DeleteSelection : ReplaceRuleIntent
|
||||||
|
data object UploadSelection : ReplaceRuleIntent
|
||||||
|
data class ExportSelection(val uri: Uri) : ReplaceRuleIntent
|
||||||
|
data class MoveItem(val from: Int, val to: Int) : ReplaceRuleIntent
|
||||||
|
data object SaveSortOrder : ReplaceRuleIntent
|
||||||
|
data class DeleteRule(val rule: ReplaceRule) : ReplaceRuleIntent
|
||||||
|
data class SetRuleEnabled(val rule: ReplaceRule, val enabled: Boolean) : ReplaceRuleIntent
|
||||||
|
data class CopyRule(val rule: ReplaceRule) : ReplaceRuleIntent
|
||||||
|
data class ImportSource(val text: String) : ReplaceRuleIntent
|
||||||
|
data object CancelImport : ReplaceRuleIntent
|
||||||
|
data class ToggleImportSelection(val index: Int) : ReplaceRuleIntent
|
||||||
|
data class ToggleImportAll(val isSelected: Boolean) : ReplaceRuleIntent
|
||||||
|
data class UpdateImportItem(val index: Int, val rule: ReplaceRule) : ReplaceRuleIntent
|
||||||
|
data object SaveImportedRules : ReplaceRuleIntent
|
||||||
|
// ReplaceRule-specific
|
||||||
|
data class SetGroup(val groupName: String?) : ReplaceRuleIntent
|
||||||
|
data class SetSortMode(val mode: String) : ReplaceRuleIntent
|
||||||
|
data class ToTop(val rule: ReplaceRule) : ReplaceRuleIntent
|
||||||
|
data class ToBottom(val rule: ReplaceRule) : ReplaceRuleIntent
|
||||||
|
data class TopSelectByIds(val ids: Set<Long>) : ReplaceRuleIntent
|
||||||
|
data class BottomSelectByIds(val ids: Set<Long>) : ReplaceRuleIntent
|
||||||
|
data class AddGroup(val group: String) : ReplaceRuleIntent
|
||||||
|
data class DeleteGroup(val group: String) : ReplaceRuleIntent
|
||||||
|
data class UpGroup(val oldGroup: String, val newGroup: String?) : ReplaceRuleIntent
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface ReplaceRuleEffect
|
||||||
@@ -6,7 +6,6 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@@ -15,7 +14,6 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
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.FileOpen
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
@@ -57,7 +55,6 @@ import io.legado.app.ui.widget.components.alert.AppAlertDialog
|
|||||||
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
|
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
|
||||||
import io.legado.app.ui.widget.components.divider.PillDivider
|
import io.legado.app.ui.widget.components.divider.PillDivider
|
||||||
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
|
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
|
||||||
import io.legado.app.ui.widget.components.icon.AppIcon
|
|
||||||
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
|
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
|
||||||
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
|
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.importComponents.SourceInputDialog
|
||||||
@@ -66,16 +63,47 @@ import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
|||||||
import io.legado.app.ui.widget.components.rules.RuleListScaffold
|
import io.legado.app.ui.widget.components.rules.RuleListScaffold
|
||||||
import io.legado.app.ui.widget.components.tabRow.AppTabRow
|
import io.legado.app.ui.widget.components.tabRow.AppTabRow
|
||||||
import io.legado.app.ui.widget.components.text.AppText
|
import io.legado.app.ui.widget.components.text.AppText
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
|
@OptIn(
|
||||||
|
ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
|
||||||
|
ExperimentalMaterial3ExpressiveApi::class
|
||||||
|
)
|
||||||
|
@Composable
|
||||||
|
fun ReplaceRuleRouteScreen(
|
||||||
|
viewModel: ReplaceRuleViewModel = koinViewModel(),
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
onNavigateToEdit: (ReplaceEditRoute) -> Unit,
|
||||||
|
) {
|
||||||
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
val importState by viewModel.importState.collectAsStateWithLifecycle()
|
||||||
|
val groups by viewModel.allGroups.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
ReplaceRuleScreen(
|
||||||
|
state = uiState,
|
||||||
|
importState = importState,
|
||||||
|
events = viewModel.events,
|
||||||
|
groups = groups,
|
||||||
|
onIntent = viewModel::onIntent,
|
||||||
|
onBackClick = onBackClick,
|
||||||
|
onNavigateToEdit = onNavigateToEdit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(
|
||||||
|
ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
|
||||||
ExperimentalMaterial3ExpressiveApi::class
|
ExperimentalMaterial3ExpressiveApi::class
|
||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReplaceRuleScreen(
|
fun ReplaceRuleScreen(
|
||||||
viewModel: ReplaceRuleViewModel = koinViewModel(),
|
state: ReplaceRuleUiState,
|
||||||
|
importState: BaseImportUiState<ReplaceRule>,
|
||||||
|
events: Flow<BaseRuleEvent>,
|
||||||
|
groups: List<String>,
|
||||||
|
onIntent: (ReplaceRuleIntent) -> Unit,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
onNavigateToEdit: (ReplaceEditRoute) -> Unit,
|
onNavigateToEdit: (ReplaceEditRoute) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -83,10 +111,8 @@ fun ReplaceRuleScreen(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val rules = state.items
|
||||||
val rules = uiState.items
|
val selectedIds = state.selectedIds
|
||||||
val groups by viewModel.allGroups.collectAsStateWithLifecycle()
|
|
||||||
val selectedIds = uiState.selectedIds
|
|
||||||
val inSelectionMode = selectedIds.isNotEmpty()
|
val inSelectionMode = selectedIds.isNotEmpty()
|
||||||
|
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
@@ -101,18 +127,16 @@ fun ReplaceRuleScreen(
|
|||||||
var showDeleteRuleDialog by remember { mutableStateOf<ReplaceRule?>(null) }
|
var showDeleteRuleDialog by remember { mutableStateOf<ReplaceRule?>(null) }
|
||||||
var showGroupManageSheet by remember { mutableStateOf(false) }
|
var showGroupManageSheet by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val importState by viewModel.importState.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
var selectedTabIndex by remember { mutableIntStateOf(0) }
|
var selectedTabIndex by remember { mutableIntStateOf(0) }
|
||||||
val tabItems = remember(groups) { listOf("全部") + groups }
|
val tabItems = remember(groups) { listOf("全部") + groups }
|
||||||
|
|
||||||
val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
|
val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
|
||||||
viewModel.moveItemInList(from.index, to.index)
|
onIntent(ReplaceRuleIntent.MoveItem(from.index, to.index))
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.SegmentFrequentTick)
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.SegmentFrequentTick)
|
||||||
}
|
}
|
||||||
|
|
||||||
val canReorder = remember(uiState.sortMode) {
|
val canReorder = remember(state.sortMode) {
|
||||||
uiState.sortMode == "asc" || uiState.sortMode == "desc"
|
state.sortMode == "asc" || state.sortMode == "desc"
|
||||||
}
|
}
|
||||||
|
|
||||||
val importDoc = rememberLauncherForActivityResult(
|
val importDoc = rememberLauncherForActivityResult(
|
||||||
@@ -121,7 +145,7 @@ fun ReplaceRuleScreen(
|
|||||||
uri?.let {
|
uri?.let {
|
||||||
context.contentResolver.openInputStream(it)?.use { stream ->
|
context.contentResolver.openInputStream(it)?.use { stream ->
|
||||||
val text = stream.reader().readText()
|
val text = stream.reader().readText()
|
||||||
viewModel.importSource(text)
|
onIntent(ReplaceRuleIntent.ImportSource(text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +154,7 @@ fun ReplaceRuleScreen(
|
|||||||
val exportDoc = rememberLauncherForActivityResult(
|
val exportDoc = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.CreateDocument("application/json"),
|
contract = ActivityResultContracts.CreateDocument("application/json"),
|
||||||
onResult = { uri ->
|
onResult = { uri ->
|
||||||
uri?.let { viewModel.exportToUri(it, rules, selectedIds) }
|
uri?.let { onIntent(ReplaceRuleIntent.ExportSelection(it)) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -140,7 +164,7 @@ fun ReplaceRuleScreen(
|
|||||||
onDismissRequest = { showUrlInput = false },
|
onDismissRequest = { showUrlInput = false },
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
showUrlInput = false
|
showUrlInput = false
|
||||||
viewModel.importSource(it)
|
onIntent(ReplaceRuleIntent.ImportSource(it))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -168,7 +192,7 @@ fun ReplaceRuleScreen(
|
|||||||
},
|
},
|
||||||
onUpload = {
|
onUpload = {
|
||||||
showExportSheet = false
|
showExportSheet = false
|
||||||
viewModel.uploadSelectedRules(selectedIds, rules)
|
onIntent(ReplaceRuleIntent.UploadSelection)
|
||||||
},
|
},
|
||||||
allowExtensions = arrayOf("json")
|
allowExtensions = arrayOf("json")
|
||||||
)
|
)
|
||||||
@@ -176,11 +200,11 @@ fun ReplaceRuleScreen(
|
|||||||
BatchImportDialog(
|
BatchImportDialog(
|
||||||
title = stringResource(R.string.import_replace_rule),
|
title = stringResource(R.string.import_replace_rule),
|
||||||
importState = importState,
|
importState = importState,
|
||||||
onDismissRequest = { viewModel.cancelImport() },
|
onDismissRequest = { onIntent(ReplaceRuleIntent.CancelImport) },
|
||||||
onToggleItem = { viewModel.toggleImportSelection(it) },
|
onToggleItem = { onIntent(ReplaceRuleIntent.ToggleImportSelection(it)) },
|
||||||
onToggleAll = { viewModel.toggleImportAll(it) },
|
onToggleAll = { onIntent(ReplaceRuleIntent.ToggleImportAll(it)) },
|
||||||
onUpdateItem = { index, rule -> viewModel.updateImportItem(index, rule) },
|
onUpdateItem = { index, rule -> onIntent(ReplaceRuleIntent.UpdateImportItem(index, rule)) },
|
||||||
onConfirm = { viewModel.saveImportedRules() },
|
onConfirm = { onIntent(ReplaceRuleIntent.SaveImportedRules) },
|
||||||
topBarActions = {},
|
topBarActions = {},
|
||||||
itemTitle = { rule -> rule.name },
|
itemTitle = { rule -> rule.name },
|
||||||
itemSubtitle = { rule ->
|
itemSubtitle = { rule ->
|
||||||
@@ -189,7 +213,7 @@ fun ReplaceRuleScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (importState is BaseImportUiState.Loading) {
|
if (importState is BaseImportUiState.Loading) {
|
||||||
Dialog(onDismissRequest = { viewModel.cancelImport() }) { LoadingIndicator() }
|
Dialog(onDismissRequest = { onIntent(ReplaceRuleIntent.CancelImport) }) { LoadingIndicator() }
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(importState) {
|
LaunchedEffect(importState) {
|
||||||
@@ -197,13 +221,13 @@ fun ReplaceRuleScreen(
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
snackbarHostState.showSnackbar(it.msg)
|
snackbarHostState.showSnackbar(it.msg)
|
||||||
}
|
}
|
||||||
viewModel.cancelImport()
|
onIntent(ReplaceRuleIntent.CancelImport)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(reorderableState.isAnyItemDragging) {
|
LaunchedEffect(reorderableState.isAnyItemDragging) {
|
||||||
if (!reorderableState.isAnyItemDragging) {
|
if (!reorderableState.isAnyItemDragging) {
|
||||||
viewModel.saveSortOrder()
|
onIntent(ReplaceRuleIntent.SaveSortOrder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,12 +235,12 @@ fun ReplaceRuleScreen(
|
|||||||
val maxIndex = groups.size
|
val maxIndex = groups.size
|
||||||
if (selectedTabIndex > maxIndex) {
|
if (selectedTabIndex > maxIndex) {
|
||||||
selectedTabIndex = 0
|
selectedTabIndex = 0
|
||||||
viewModel.setGroup("全部")
|
onIntent(ReplaceRuleIntent.SetGroup("全部"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.events.collect { event ->
|
events.collect { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
is BaseRuleEvent.ShowSnackbar -> {
|
is BaseRuleEvent.ShowSnackbar -> {
|
||||||
val result = snackbarHostState.showSnackbar(
|
val result = snackbarHostState.showSnackbar(
|
||||||
@@ -244,8 +268,8 @@ fun ReplaceRuleScreen(
|
|||||||
show = showGroupManageSheet,
|
show = showGroupManageSheet,
|
||||||
groups = groups,
|
groups = groups,
|
||||||
onDismissRequest = { showGroupManageSheet = false },
|
onDismissRequest = { showGroupManageSheet = false },
|
||||||
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
|
onUpdateGroup = { old, new -> onIntent(ReplaceRuleIntent.UpGroup(old, new)) },
|
||||||
onDeleteGroup = { viewModel.delGroup(it) }
|
onDeleteGroup = { onIntent(ReplaceRuleIntent.DeleteGroup(it)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -255,7 +279,7 @@ fun ReplaceRuleScreen(
|
|||||||
title = stringResource(R.string.delete),
|
title = stringResource(R.string.delete),
|
||||||
confirmText = stringResource(R.string.ok),
|
confirmText = stringResource(R.string.ok),
|
||||||
onConfirm = { rule ->
|
onConfirm = { rule ->
|
||||||
viewModel.delete(rule)
|
onIntent(ReplaceRuleIntent.DeleteRule(rule))
|
||||||
showDeleteRuleDialog = null
|
showDeleteRuleDialog = null
|
||||||
},
|
},
|
||||||
dismissText = stringResource(R.string.cancel),
|
dismissText = stringResource(R.string.cancel),
|
||||||
@@ -264,42 +288,30 @@ fun ReplaceRuleScreen(
|
|||||||
|
|
||||||
RuleListScaffold(
|
RuleListScaffold(
|
||||||
title = "替换规则",
|
title = "替换规则",
|
||||||
state = uiState,
|
state = state,
|
||||||
onBackClick = { onBackClick() },
|
onBackClick = { onBackClick() },
|
||||||
onSearchToggle = { viewModel.setSearchMode(!uiState.isSearch) },
|
onSearchToggle = { onIntent(ReplaceRuleIntent.SetSearchMode(!state.isSearch)) },
|
||||||
onSearchQueryChange = { viewModel.setSearchKey(it) },
|
onSearchQueryChange = { onIntent(ReplaceRuleIntent.UpdateSearchQuery(it)) },
|
||||||
searchPlaceholder = stringResource(R.string.replace_purify_search),
|
searchPlaceholder = stringResource(R.string.replace_purify_search),
|
||||||
onClearSelection = { viewModel.setSelection(emptySet()) },
|
onClearSelection = { onIntent(ReplaceRuleIntent.ClearSelection) },
|
||||||
onSelectAll = { viewModel.setSelection(rules.map { it.id }.toSet()) },
|
onSelectAll = { onIntent(ReplaceRuleIntent.SelectAll) },
|
||||||
onSelectInvert = { viewModel.setSelection(rules.map { it.id }.toSet() - selectedIds) },
|
onSelectInvert = { onIntent(ReplaceRuleIntent.InvertSelection) },
|
||||||
selectionSecondaryActions = listOf(
|
selectionSecondaryActions = listOf(
|
||||||
ActionItem(
|
ActionItem(
|
||||||
text = stringResource(R.string.enable),
|
text = stringResource(R.string.enable),
|
||||||
onClick = {
|
onClick = { onIntent(ReplaceRuleIntent.EnableSelection) }
|
||||||
viewModel.enableSelectionByIds(selectedIds)
|
|
||||||
viewModel.setSelection(emptySet())
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
ActionItem(
|
ActionItem(
|
||||||
text = stringResource(R.string.disable_selection),
|
text = stringResource(R.string.disable_selection),
|
||||||
onClick = {
|
onClick = { onIntent(ReplaceRuleIntent.DisableSelection) }
|
||||||
viewModel.disableSelectionByIds(selectedIds)
|
|
||||||
viewModel.setSelection(emptySet())
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
ActionItem(
|
ActionItem(
|
||||||
text = stringResource(R.string.to_top),
|
text = stringResource(R.string.to_top),
|
||||||
onClick = {
|
onClick = { onIntent(ReplaceRuleIntent.TopSelectByIds(selectedIds)) }
|
||||||
viewModel.topSelectByIds(selectedIds)
|
|
||||||
viewModel.setSelection(emptySet())
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
ActionItem(
|
ActionItem(
|
||||||
text = stringResource(R.string.to_bottom),
|
text = stringResource(R.string.to_bottom),
|
||||||
onClick = {
|
onClick = { onIntent(ReplaceRuleIntent.BottomSelectByIds(selectedIds)) }
|
||||||
viewModel.bottomSelectByIds(selectedIds)
|
|
||||||
viewModel.setSelection(emptySet())
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
ActionItem(
|
ActionItem(
|
||||||
text = stringResource(R.string.export),
|
text = stringResource(R.string.export),
|
||||||
@@ -308,8 +320,8 @@ fun ReplaceRuleScreen(
|
|||||||
),
|
),
|
||||||
onDeleteSelected = { ids ->
|
onDeleteSelected = { ids ->
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
viewModel.delSelectionByIds(ids as Set<Long>)
|
onIntent(ReplaceRuleIntent.SetSelection(ids as Set<Long>))
|
||||||
viewModel.setSelection(emptySet())
|
onIntent(ReplaceRuleIntent.DeleteSelection)
|
||||||
},
|
},
|
||||||
bottomContent = {
|
bottomContent = {
|
||||||
if (tabItems.size > 1) {
|
if (tabItems.size > 1) {
|
||||||
@@ -320,7 +332,7 @@ fun ReplaceRuleScreen(
|
|||||||
selectedTabIndex = selectedTabIndex,
|
selectedTabIndex = selectedTabIndex,
|
||||||
onTabSelected = { index ->
|
onTabSelected = { index ->
|
||||||
selectedTabIndex = index
|
selectedTabIndex = index
|
||||||
viewModel.setGroup(tabItems[index])
|
onIntent(ReplaceRuleIntent.SetGroup(tabItems[index]))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -355,16 +367,16 @@ fun ReplaceRuleScreen(
|
|||||||
PillDivider()
|
PillDivider()
|
||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = "旧的在前",
|
text = "旧的在前",
|
||||||
onClick = { viewModel.setSortMode("asc"); dismiss() }
|
onClick = { onIntent(ReplaceRuleIntent.SetSortMode("asc")); dismiss() }
|
||||||
)
|
)
|
||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = "新的在前",
|
text = "新的在前",
|
||||||
onClick = { viewModel.setSortMode("desc"); dismiss() }
|
onClick = { onIntent(ReplaceRuleIntent.SetSortMode("desc")); dismiss() }
|
||||||
)
|
)
|
||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = "名称升序",
|
text = "名称升序",
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.setSortMode("name_asc")
|
onIntent(ReplaceRuleIntent.SetSortMode("name_asc"))
|
||||||
dismiss()
|
dismiss()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
snackbarHostState.showSnackbar("当前排序模式下禁用拖动")
|
snackbarHostState.showSnackbar("当前排序模式下禁用拖动")
|
||||||
@@ -374,7 +386,7 @@ fun ReplaceRuleScreen(
|
|||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = "名称降序",
|
text = "名称降序",
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.setSortMode("name_desc")
|
onIntent(ReplaceRuleIntent.SetSortMode("name_desc"))
|
||||||
dismiss()
|
dismiss()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
snackbarHostState.showSnackbar("当前排序模式下禁用拖动")
|
snackbarHostState.showSnackbar("当前排序模式下禁用拖动")
|
||||||
@@ -407,10 +419,10 @@ fun ReplaceRuleScreen(
|
|||||||
inSelectionMode = inSelectionMode,
|
inSelectionMode = inSelectionMode,
|
||||||
canReorder = canReorder,
|
canReorder = canReorder,
|
||||||
onToggleSelection = {
|
onToggleSelection = {
|
||||||
viewModel.toggleSelection(ui.id)
|
onIntent(ReplaceRuleIntent.ToggleSelection(ui.id))
|
||||||
},
|
},
|
||||||
onEnabledChange = { enabled ->
|
onEnabledChange = { enabled ->
|
||||||
viewModel.update(ui.rule.copy(isEnabled = enabled))
|
onIntent(ReplaceRuleIntent.SetRuleEnabled(ui.rule, enabled))
|
||||||
},
|
},
|
||||||
onClickEdit = {
|
onClickEdit = {
|
||||||
onNavigateToEdit(
|
onNavigateToEdit(
|
||||||
@@ -424,11 +436,11 @@ fun ReplaceRuleScreen(
|
|||||||
dropdownContent = { dismiss ->
|
dropdownContent = { dismiss ->
|
||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = "移至顶部",
|
text = "移至顶部",
|
||||||
onClick = { viewModel.toTop(ui.rule); dismiss() }
|
onClick = { onIntent(ReplaceRuleIntent.ToTop(ui.rule)); dismiss() }
|
||||||
)
|
)
|
||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = "移至底部",
|
text = "移至底部",
|
||||||
onClick = { viewModel.toBottom(ui.rule); dismiss() }
|
onClick = { onIntent(ReplaceRuleIntent.ToBottom(ui.rule)); dismiss() }
|
||||||
)
|
)
|
||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = "删除",
|
text = "删除",
|
||||||
@@ -443,7 +455,7 @@ fun ReplaceRuleScreen(
|
|||||||
listState = listState,
|
listState = listState,
|
||||||
items = rules,
|
items = rules,
|
||||||
selectedIds = selectedIds,
|
selectedIds = selectedIds,
|
||||||
onSelectionChange = { viewModel.setSelection(it) },
|
onSelectionChange = { onIntent(ReplaceRuleIntent.SetSelection(it)) },
|
||||||
idProvider = { it.id },
|
idProvider = { it.id },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package io.legado.app.ui.replace
|
package io.legado.app.ui.replace
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.compose.runtime.Immutable
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import io.legado.app.base.BaseRuleEvent
|
import io.legado.app.base.BaseRuleEvent
|
||||||
import io.legado.app.base.BaseRuleViewModel
|
import io.legado.app.base.BaseRuleViewModel
|
||||||
@@ -14,8 +13,6 @@ import io.legado.app.data.repository.UploadRepository
|
|||||||
import io.legado.app.help.ReplaceAnalyzer
|
import io.legado.app.help.ReplaceAnalyzer
|
||||||
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
|
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
|
||||||
import io.legado.app.ui.widget.components.list.InteractionState
|
import io.legado.app.ui.widget.components.list.InteractionState
|
||||||
import io.legado.app.ui.widget.components.list.ListUiState
|
|
||||||
import io.legado.app.ui.widget.components.list.SelectableItem
|
|
||||||
import io.legado.app.utils.GSON
|
import io.legado.app.utils.GSON
|
||||||
import io.legado.app.utils.getPrefString
|
import io.legado.app.utils.getPrefString
|
||||||
import io.legado.app.utils.isJsonArray
|
import io.legado.app.utils.isJsonArray
|
||||||
@@ -37,27 +34,6 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@Immutable
|
|
||||||
data class ReplaceRuleItemUi(
|
|
||||||
override val id: Long,
|
|
||||||
val name: String,
|
|
||||||
val isEnabled: Boolean,
|
|
||||||
val group: String?,
|
|
||||||
val rule: ReplaceRule
|
|
||||||
) : SelectableItem<Long>
|
|
||||||
|
|
||||||
data class ReplaceRuleUiState(
|
|
||||||
override val items: List<ReplaceRuleItemUi> = emptyList(),
|
|
||||||
override val selectedIds: Set<Long> = emptySet(),
|
|
||||||
override val searchKey: String = "",
|
|
||||||
val sortMode: String = "desc",
|
|
||||||
val groups: List<String> = emptyList(),
|
|
||||||
val interaction: InteractionState = InteractionState()
|
|
||||||
) : ListUiState<ReplaceRuleItemUi> {
|
|
||||||
override val isSearch: Boolean get() = interaction.isSearchMode
|
|
||||||
override val isLoading: Boolean get() = interaction.isUploading
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReplaceRuleViewModel(
|
class ReplaceRuleViewModel(
|
||||||
application: Application,
|
application: Application,
|
||||||
uploadRepository: UploadRepository
|
uploadRepository: UploadRepository
|
||||||
@@ -68,6 +44,7 @@ class ReplaceRuleViewModel(
|
|||||||
) {
|
) {
|
||||||
private val repository = ReplaceRuleRepository()
|
private val repository = ReplaceRuleRepository()
|
||||||
private val _sortMode = MutableStateFlow(context.getPrefString(PreferKey.replaceSortMode, "desc") ?: "desc")
|
private val _sortMode = MutableStateFlow(context.getPrefString(PreferKey.replaceSortMode, "desc") ?: "desc")
|
||||||
|
val sortMode = _sortMode.asStateFlow()
|
||||||
private val _group = MutableStateFlow<String?>(null)
|
private val _group = MutableStateFlow<String?>(null)
|
||||||
val group = _group.asStateFlow()
|
val group = _group.asStateFlow()
|
||||||
|
|
||||||
@@ -78,7 +55,60 @@ class ReplaceRuleViewModel(
|
|||||||
initialValue = emptyList()
|
initialValue = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun setGroup(groupName: String?) {
|
fun onIntent(intent: ReplaceRuleIntent) {
|
||||||
|
when (intent) {
|
||||||
|
is ReplaceRuleIntent.SetSearchMode -> setSearchMode(intent.active)
|
||||||
|
is ReplaceRuleIntent.UpdateSearchQuery -> setSearchKey(intent.query)
|
||||||
|
ReplaceRuleIntent.ClearSelection -> setSelection(emptySet())
|
||||||
|
ReplaceRuleIntent.SelectAll -> selectAll()
|
||||||
|
ReplaceRuleIntent.InvertSelection -> invertSelection()
|
||||||
|
is ReplaceRuleIntent.SetSelection -> setSelection(intent.ids)
|
||||||
|
is ReplaceRuleIntent.ToggleSelection -> toggleSelection(intent.id)
|
||||||
|
ReplaceRuleIntent.EnableSelection -> {
|
||||||
|
enableSelectionByIds(uiState.value.selectedIds)
|
||||||
|
setSelection(emptySet())
|
||||||
|
}
|
||||||
|
ReplaceRuleIntent.DisableSelection -> {
|
||||||
|
disableSelectionByIds(uiState.value.selectedIds)
|
||||||
|
setSelection(emptySet())
|
||||||
|
}
|
||||||
|
ReplaceRuleIntent.DeleteSelection -> {
|
||||||
|
delSelectionByIds(uiState.value.selectedIds)
|
||||||
|
setSelection(emptySet())
|
||||||
|
}
|
||||||
|
ReplaceRuleIntent.UploadSelection -> {
|
||||||
|
val state = uiState.value
|
||||||
|
uploadSelectedRules(state.selectedIds, state.items)
|
||||||
|
}
|
||||||
|
is ReplaceRuleIntent.ExportSelection -> {
|
||||||
|
val state = uiState.value
|
||||||
|
exportToUri(intent.uri, state.items, state.selectedIds)
|
||||||
|
}
|
||||||
|
is ReplaceRuleIntent.MoveItem -> moveItemInList(intent.from, intent.to)
|
||||||
|
ReplaceRuleIntent.SaveSortOrder -> saveSortOrder()
|
||||||
|
is ReplaceRuleIntent.DeleteRule -> delete(intent.rule)
|
||||||
|
is ReplaceRuleIntent.SetRuleEnabled -> update(intent.rule.copy(isEnabled = intent.enabled))
|
||||||
|
is ReplaceRuleIntent.CopyRule -> { /* not implemented for ReplaceRule */ }
|
||||||
|
is ReplaceRuleIntent.ImportSource -> importSource(intent.text)
|
||||||
|
ReplaceRuleIntent.CancelImport -> cancelImport()
|
||||||
|
is ReplaceRuleIntent.ToggleImportSelection -> toggleImportSelection(intent.index)
|
||||||
|
is ReplaceRuleIntent.ToggleImportAll -> toggleImportAll(intent.isSelected)
|
||||||
|
is ReplaceRuleIntent.UpdateImportItem -> updateImportItem(intent.index, intent.rule)
|
||||||
|
ReplaceRuleIntent.SaveImportedRules -> saveImportedRules()
|
||||||
|
// ReplaceRule-specific
|
||||||
|
is ReplaceRuleIntent.SetGroup -> setGroup(intent.groupName)
|
||||||
|
is ReplaceRuleIntent.SetSortMode -> setSortMode(intent.mode)
|
||||||
|
is ReplaceRuleIntent.ToTop -> toTop(intent.rule)
|
||||||
|
is ReplaceRuleIntent.ToBottom -> toBottom(intent.rule)
|
||||||
|
is ReplaceRuleIntent.TopSelectByIds -> topSelectByIds(intent.ids)
|
||||||
|
is ReplaceRuleIntent.BottomSelectByIds -> bottomSelectByIds(intent.ids)
|
||||||
|
is ReplaceRuleIntent.AddGroup -> addGroup(intent.group)
|
||||||
|
is ReplaceRuleIntent.DeleteGroup -> delGroup(intent.group)
|
||||||
|
is ReplaceRuleIntent.UpGroup -> upGroup(intent.oldGroup, intent.newGroup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setGroup(groupName: String?) {
|
||||||
_group.value = if (groupName == "全部" || groupName.isNullOrBlank()) {
|
_group.value = if (groupName == "全部" || groupName.isNullOrBlank()) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
@@ -129,6 +159,7 @@ class ReplaceRuleViewModel(
|
|||||||
items = items,
|
items = items,
|
||||||
selectedIds = selectedIds,
|
selectedIds = selectedIds,
|
||||||
searchKey = _searchKey.value,
|
searchKey = _searchKey.value,
|
||||||
|
sortMode = _sortMode.value,
|
||||||
interaction = InteractionState(
|
interaction = InteractionState(
|
||||||
isSearchMode = isSearch,
|
isSearchMode = isSearch,
|
||||||
isUploading = isUploading || (importState is BaseImportUiState.Loading),
|
isUploading = isUploading || (importState is BaseImportUiState.Loading),
|
||||||
@@ -209,12 +240,12 @@ class ReplaceRuleViewModel(
|
|||||||
return if (comparator != null) rules.sortedWith(comparator) else rules
|
return if (comparator != null) rules.sortedWith(comparator) else rules
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSortMode(mode: String) {
|
private fun setSortMode(mode: String) {
|
||||||
_sortMode.value = mode
|
_sortMode.value = mode
|
||||||
context.putPrefString(PreferKey.replaceSortMode, mode)
|
context.putPrefString(PreferKey.replaceSortMode, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveSortOrder() {
|
private fun saveSortOrder() {
|
||||||
val currentLocal = _localItems.value ?: return
|
val currentLocal = _localItems.value ?: return
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
repository.moveOrder(currentLocal.map { it.rule }, _sortMode.value == "desc")
|
repository.moveOrder(currentLocal.map { it.rule }, _sortMode.value == "desc")
|
||||||
@@ -223,8 +254,8 @@ class ReplaceRuleViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun update(vararg rule: ReplaceRule) = viewModelScope.launch { repository.update(*rule) }
|
private fun update(vararg rule: ReplaceRule) = viewModelScope.launch { repository.update(*rule) }
|
||||||
fun delete(rule: ReplaceRule) = viewModelScope.launch { repository.delete(rule) }
|
private fun delete(rule: ReplaceRule) = viewModelScope.launch { repository.delete(rule) }
|
||||||
fun enableSelectionByIds(ids: Set<Long>) = viewModelScope.launch { repository.enableByIds(ids) }
|
fun enableSelectionByIds(ids: Set<Long>) = viewModelScope.launch { repository.enableByIds(ids) }
|
||||||
fun disableSelectionByIds(ids: Set<Long>) =
|
fun disableSelectionByIds(ids: Set<Long>) =
|
||||||
viewModelScope.launch { repository.disableByIds(ids) }
|
viewModelScope.launch { repository.disableByIds(ids) }
|
||||||
@@ -234,22 +265,30 @@ class ReplaceRuleViewModel(
|
|||||||
_selectedIds.update { it - ids }
|
_selectedIds.update { it - ids }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addGroup(group: String) = viewModelScope.launch { repository.addGroup(group) }
|
private fun selectAll() {
|
||||||
fun delGroup(group: String) = viewModelScope.launch { repository.delGroup(group) }
|
setSelection(uiState.value.items.map { it.id }.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
fun toTop(rule: ReplaceRule) =
|
private fun invertSelection() {
|
||||||
|
val state = uiState.value
|
||||||
|
setSelection(state.items.map { it.id }.toSet() - state.selectedIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addGroup(group: String) = viewModelScope.launch { repository.addGroup(group) }
|
||||||
|
private fun delGroup(group: String) = viewModelScope.launch { repository.delGroup(group) }
|
||||||
|
|
||||||
|
private fun toTop(rule: ReplaceRule) =
|
||||||
viewModelScope.launch { repository.toTop(rule, _sortMode.value == "desc") }
|
viewModelScope.launch { repository.toTop(rule, _sortMode.value == "desc") }
|
||||||
|
|
||||||
fun toBottom(rule: ReplaceRule) =
|
private fun toBottom(rule: ReplaceRule) =
|
||||||
viewModelScope.launch { repository.toBottom(rule, _sortMode.value == "desc") }
|
viewModelScope.launch { repository.toBottom(rule, _sortMode.value == "desc") }
|
||||||
|
|
||||||
fun upOrder() = viewModelScope.launch { repository.upOrder() }
|
private fun topSelectByIds(ids: Set<Long>) =
|
||||||
fun topSelectByIds(ids: Set<Long>) =
|
|
||||||
viewModelScope.launch { repository.topByIds(ids, _sortMode.value == "desc") }
|
viewModelScope.launch { repository.topByIds(ids, _sortMode.value == "desc") }
|
||||||
|
|
||||||
fun bottomSelectByIds(ids: Set<Long>) =
|
private fun bottomSelectByIds(ids: Set<Long>) =
|
||||||
viewModelScope.launch { repository.bottomByIds(ids, _sortMode.value == "desc") }
|
viewModelScope.launch { repository.bottomByIds(ids, _sortMode.value == "desc") }
|
||||||
|
|
||||||
fun upGroup(oldGroup: String, newGroup: String?) =
|
private fun upGroup(oldGroup: String, newGroup: String?) =
|
||||||
viewModelScope.launch { repository.upGroup(oldGroup, newGroup) }
|
viewModelScope.launch { repository.upGroup(oldGroup, newGroup) }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package io.legado.app.ui.replace.edit
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class ReplaceEditUiState(
|
||||||
|
val id: Long = 0,
|
||||||
|
val name: String = "",
|
||||||
|
val group: String = "默认",
|
||||||
|
val pattern: String = "",
|
||||||
|
val replacement: String = "",
|
||||||
|
val isRegex: Boolean = false,
|
||||||
|
val scope: String = "",
|
||||||
|
val scopeTitle: Boolean = false,
|
||||||
|
val scopeContent: Boolean = false,
|
||||||
|
val excludeScope: String = "",
|
||||||
|
val timeout: String = "3000",
|
||||||
|
val allGroups: List<String> = emptyList(),
|
||||||
|
val showGroupDialog: Boolean = false,
|
||||||
|
val activeField: ActiveField = ActiveField.None
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class ActiveField { Name, None, Pattern, Replacement, Scope, Exclude }
|
||||||
|
|
||||||
|
sealed interface ReplaceEditIntent {
|
||||||
|
data class OnNameChange(val value: String) : ReplaceEditIntent
|
||||||
|
data class OnPatternChange(val value: String) : ReplaceEditIntent
|
||||||
|
data class OnReplacementChange(val value: String) : ReplaceEditIntent
|
||||||
|
data class OnScopeChange(val value: String) : ReplaceEditIntent
|
||||||
|
data class OnExcludeScopeChange(val value: String) : ReplaceEditIntent
|
||||||
|
data class OnGroupChange(val value: String) : ReplaceEditIntent
|
||||||
|
data class OnRegexChange(val value: Boolean) : ReplaceEditIntent
|
||||||
|
data class OnScopeTitleChange(val value: Boolean) : ReplaceEditIntent
|
||||||
|
data class OnScopeContentChange(val value: Boolean) : ReplaceEditIntent
|
||||||
|
data class OnTimeoutChange(val value: String) : ReplaceEditIntent
|
||||||
|
data class SetActiveField(val field: ActiveField) : ReplaceEditIntent
|
||||||
|
data class InsertTextAtCursor(val text: String) : ReplaceEditIntent
|
||||||
|
data class ToggleGroupDialog(val show: Boolean) : ReplaceEditIntent
|
||||||
|
data class DeleteGroups(val groups: List<String>) : ReplaceEditIntent
|
||||||
|
data object CopyRule : ReplaceEditIntent
|
||||||
|
data object PasteRule : ReplaceEditIntent
|
||||||
|
data object Save : ReplaceEditIntent
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface ReplaceEditEffect {
|
||||||
|
data object NavigateBack : ReplaceEditEffect
|
||||||
|
}
|
||||||
@@ -35,8 +35,8 @@ import androidx.compose.material3.ExposedDropdownMenuBox
|
|||||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||||
import androidx.compose.material3.animateFloatingActionButton
|
import androidx.compose.material3.animateFloatingActionButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -65,6 +65,9 @@ import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
|||||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||||
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.topbar.TopBarNavigationButton
|
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -75,12 +78,35 @@ fun keyboardAsState(): State<Boolean> {
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReplaceEditScreen(
|
fun ReplaceEditRouteScreen(
|
||||||
|
viewModel: ReplaceEditViewModel = koinViewModel(),
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onSaveSuccess: () -> Unit,
|
onSaveSuccess: () -> Unit,
|
||||||
viewModel: ReplaceEditViewModel = koinViewModel()
|
|
||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsState()
|
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.effects.collectLatest { effect ->
|
||||||
|
when (effect) {
|
||||||
|
ReplaceEditEffect.NavigateBack -> onSaveSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplaceEditScreen(
|
||||||
|
state = state,
|
||||||
|
onIntent = viewModel::onIntent,
|
||||||
|
onBack = onBack,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||||
|
@Composable
|
||||||
|
fun ReplaceEditScreen(
|
||||||
|
state: ReplaceEditUiState,
|
||||||
|
onIntent: (ReplaceEditIntent) -> Unit,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
) {
|
||||||
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
|
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
val isKeyboardVisible by keyboardAsState()
|
val isKeyboardVisible by keyboardAsState()
|
||||||
@@ -101,9 +127,7 @@ fun ReplaceEditScreen(
|
|||||||
exit = fadeOut()
|
exit = fadeOut()
|
||||||
) {
|
) {
|
||||||
TopBarActionButton(
|
TopBarActionButton(
|
||||||
onClick = {
|
onClick = { onIntent(ReplaceEditIntent.Save) },
|
||||||
viewModel.save(onSaveSuccess)
|
|
||||||
},
|
|
||||||
imageVector = Icons.Default.Save,
|
imageVector = Icons.Default.Save,
|
||||||
contentDescription = "保存"
|
contentDescription = "保存"
|
||||||
)
|
)
|
||||||
@@ -121,14 +145,14 @@ fun ReplaceEditScreen(
|
|||||||
text = "复制规则",
|
text = "复制规则",
|
||||||
onClick = {
|
onClick = {
|
||||||
showMenu = false
|
showMenu = false
|
||||||
viewModel.copyRule()
|
onIntent(ReplaceEditIntent.CopyRule)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = "粘贴规则",
|
text = "粘贴规则",
|
||||||
onClick = {
|
onClick = {
|
||||||
showMenu = false
|
showMenu = false
|
||||||
viewModel.pasteRule(onSuccess = {})
|
onIntent(ReplaceEditIntent.PasteRule)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -144,7 +168,7 @@ fun ReplaceEditScreen(
|
|||||||
visible = !isKeyboardVisible,
|
visible = !isKeyboardVisible,
|
||||||
alignment = Alignment.BottomEnd,
|
alignment = Alignment.BottomEnd,
|
||||||
),
|
),
|
||||||
onClick = { viewModel.save(onSaveSuccess) },
|
onClick = { onIntent(ReplaceEditIntent.Save) },
|
||||||
tooltipText = stringResource(R.string.action_save),
|
tooltipText = stringResource(R.string.action_save),
|
||||||
icon = Icons.Default.Save
|
icon = Icons.Default.Save
|
||||||
)
|
)
|
||||||
@@ -168,7 +192,7 @@ fun ReplaceEditScreen(
|
|||||||
.zIndex(1f)
|
.zIndex(1f)
|
||||||
) {
|
) {
|
||||||
QuickInputBar(
|
QuickInputBar(
|
||||||
onInsert = { text -> viewModel.insertTextAtCursor(text) }
|
onInsert = { text -> onIntent(ReplaceEditIntent.InsertTextAtCursor(text)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
@@ -181,13 +205,12 @@ fun ReplaceEditScreen(
|
|||||||
|
|
||||||
AppTextField(
|
AppTextField(
|
||||||
value = state.name,
|
value = state.name,
|
||||||
onValueChange = viewModel::onNameChange,
|
onValueChange = { onIntent(ReplaceEditIntent.OnNameChange(it)) },
|
||||||
label = "规则名称",
|
label = "规则名称",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
if (it.isFocused) viewModel.activeField =
|
if (it.isFocused) onIntent(ReplaceEditIntent.SetActiveField(ActiveField.Name))
|
||||||
ReplaceEditViewModel.ActiveField.Name
|
|
||||||
},
|
},
|
||||||
singleLine = true
|
singleLine = true
|
||||||
)
|
)
|
||||||
@@ -195,33 +218,31 @@ fun ReplaceEditScreen(
|
|||||||
GroupSelector(
|
GroupSelector(
|
||||||
currentGroup = state.group,
|
currentGroup = state.group,
|
||||||
allGroups = state.allGroups,
|
allGroups = state.allGroups,
|
||||||
onGroupChange = viewModel::onGroupChange,
|
onGroupChange = { onIntent(ReplaceEditIntent.OnGroupChange(it)) },
|
||||||
onManageClick = { viewModel.toggleGroupDialog(true) }
|
onManageClick = { onIntent(ReplaceEditIntent.ToggleGroupDialog(true)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
AppTextField(
|
AppTextField(
|
||||||
value = state.pattern,
|
value = state.pattern,
|
||||||
onValueChange = viewModel::onPatternChange,
|
onValueChange = { onIntent(ReplaceEditIntent.OnPatternChange(it)) },
|
||||||
label = "匹配规则",
|
label = "匹配规则",
|
||||||
placeholder = { AppText("输入正则表达式或关键字") },
|
placeholder = { AppText("输入正则表达式或关键字") },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
if (it.isFocused) viewModel.activeField =
|
if (it.isFocused) onIntent(ReplaceEditIntent.SetActiveField(ActiveField.Pattern))
|
||||||
ReplaceEditViewModel.ActiveField.Pattern
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
AppTextField(
|
AppTextField(
|
||||||
value = state.replacement,
|
value = state.replacement,
|
||||||
onValueChange = viewModel::onReplacementChange,
|
onValueChange = { onIntent(ReplaceEditIntent.OnReplacementChange(it)) },
|
||||||
label = "替换为",
|
label = "替换为",
|
||||||
placeholder = { AppText("输入替换内容或捕获组") },
|
placeholder = { AppText("输入替换内容或捕获组") },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
if (it.isFocused) viewModel.activeField =
|
if (it.isFocused) onIntent(ReplaceEditIntent.SetActiveField(ActiveField.Replacement))
|
||||||
ReplaceEditViewModel.ActiveField.Replacement
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -234,7 +255,7 @@ fun ReplaceEditScreen(
|
|||||||
label = "标题",
|
label = "标题",
|
||||||
selected = state.scopeTitle,
|
selected = state.scopeTitle,
|
||||||
checkedContentDescription = "已选择",
|
checkedContentDescription = "已选择",
|
||||||
onToggle = { viewModel.onScopeTitleChange(!state.scopeTitle) }
|
onToggle = { onIntent(ReplaceEditIntent.OnScopeTitleChange(!state.scopeTitle)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
@@ -243,7 +264,7 @@ fun ReplaceEditScreen(
|
|||||||
label = "内容",
|
label = "内容",
|
||||||
selected = state.scopeContent,
|
selected = state.scopeContent,
|
||||||
checkedContentDescription = "已选择",
|
checkedContentDescription = "已选择",
|
||||||
onToggle = { viewModel.onScopeContentChange(!state.scopeContent) }
|
onToggle = { onIntent(ReplaceEditIntent.OnScopeContentChange(!state.scopeContent)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
@@ -252,40 +273,38 @@ fun ReplaceEditScreen(
|
|||||||
label = "使用正则",
|
label = "使用正则",
|
||||||
selected = state.isRegex,
|
selected = state.isRegex,
|
||||||
checkedContentDescription = "正则已启用",
|
checkedContentDescription = "正则已启用",
|
||||||
onToggle = { viewModel.onRegexChange(!state.isRegex) }
|
onToggle = { onIntent(ReplaceEditIntent.OnRegexChange(!state.isRegex)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AppTextField(
|
AppTextField(
|
||||||
value = state.scope,
|
value = state.scope,
|
||||||
onValueChange = viewModel::onScopeChange,
|
onValueChange = { onIntent(ReplaceEditIntent.OnScopeChange(it)) },
|
||||||
label = "特定范围",
|
label = "特定范围",
|
||||||
placeholder = { AppText("指定规则适用的范围") },
|
placeholder = { AppText("指定规则适用的范围") },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
if (it.isFocused) viewModel.activeField =
|
if (it.isFocused) onIntent(ReplaceEditIntent.SetActiveField(ActiveField.Scope))
|
||||||
ReplaceEditViewModel.ActiveField.Scope
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
AppTextField(
|
AppTextField(
|
||||||
value = state.excludeScope,
|
value = state.excludeScope,
|
||||||
onValueChange = viewModel::onExcludeScopeChange,
|
onValueChange = { onIntent(ReplaceEditIntent.OnExcludeScopeChange(it)) },
|
||||||
label = "排除范围",
|
label = "排除范围",
|
||||||
placeholder = { AppText("指定规则不适用的范围") },
|
placeholder = { AppText("指定规则不适用的范围") },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
if (it.isFocused) viewModel.activeField =
|
if (it.isFocused) onIntent(ReplaceEditIntent.SetActiveField(ActiveField.Exclude))
|
||||||
ReplaceEditViewModel.ActiveField.Exclude
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
AppTextField(
|
AppTextField(
|
||||||
value = state.timeout,
|
value = state.timeout,
|
||||||
onValueChange = viewModel::onTimeoutChange,
|
onValueChange = { onIntent(ReplaceEditIntent.OnTimeoutChange(it)) },
|
||||||
label = "超时 (ms)",
|
label = "超时 (ms)",
|
||||||
placeholder = { AppText("3000") },
|
placeholder = { AppText("3000") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
@@ -298,8 +317,8 @@ fun ReplaceEditScreen(
|
|||||||
ManageGroupDialog(
|
ManageGroupDialog(
|
||||||
show = state.showGroupDialog,
|
show = state.showGroupDialog,
|
||||||
groups = state.allGroups.filter { it != "默认" },
|
groups = state.allGroups.filter { it != "默认" },
|
||||||
onDismiss = { viewModel.toggleGroupDialog(false) },
|
onDismiss = { onIntent(ReplaceEditIntent.ToggleGroupDialog(false)) },
|
||||||
onDelete = { viewModel.deleteGroups(it) }
|
onDelete = { onIntent(ReplaceEditIntent.DeleteGroups(it)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,29 +13,14 @@ import io.legado.app.utils.getClipText
|
|||||||
import io.legado.app.utils.sendToClip
|
import io.legado.app.utils.sendToClip
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
data class ReplaceEditUiState(
|
|
||||||
val id: Long = 0,
|
|
||||||
val name: String = "",
|
|
||||||
val group: String = "默认",
|
|
||||||
val pattern: String = "",
|
|
||||||
val replacement: String = "",
|
|
||||||
val isRegex: Boolean = false,
|
|
||||||
val scope: String = "",
|
|
||||||
val scopeTitle: Boolean = false,
|
|
||||||
val scopeContent: Boolean = false,
|
|
||||||
val excludeScope: String = "",
|
|
||||||
val timeout: String = "3000",
|
|
||||||
val allGroups: List<String> = emptyList(),
|
|
||||||
val showGroupDialog: Boolean = false
|
|
||||||
)
|
|
||||||
|
|
||||||
class ReplaceEditViewModel(
|
class ReplaceEditViewModel(
|
||||||
private val app: Application,
|
private val app: Application,
|
||||||
private val replaceRuleDao: ReplaceRuleDao,
|
private val replaceRuleDao: ReplaceRuleDao,
|
||||||
@@ -45,14 +30,36 @@ class ReplaceEditViewModel(
|
|||||||
private val _uiState = MutableStateFlow(ReplaceEditUiState())
|
private val _uiState = MutableStateFlow(ReplaceEditUiState())
|
||||||
val uiState = _uiState.asStateFlow()
|
val uiState = _uiState.asStateFlow()
|
||||||
|
|
||||||
var activeField: ActiveField = ActiveField.None
|
private val _effects = MutableSharedFlow<ReplaceEditEffect>(extraBufferCapacity = 16)
|
||||||
enum class ActiveField { Name, None, Pattern, Replacement, Scope, Exclude }
|
val effects = _effects.asSharedFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initData()
|
initData()
|
||||||
observeGroups()
|
observeGroups()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onIntent(intent: ReplaceEditIntent) {
|
||||||
|
when (intent) {
|
||||||
|
is ReplaceEditIntent.OnNameChange -> onNameChange(intent.value)
|
||||||
|
is ReplaceEditIntent.OnPatternChange -> onPatternChange(intent.value)
|
||||||
|
is ReplaceEditIntent.OnReplacementChange -> onReplacementChange(intent.value)
|
||||||
|
is ReplaceEditIntent.OnScopeChange -> onScopeChange(intent.value)
|
||||||
|
is ReplaceEditIntent.OnExcludeScopeChange -> onExcludeScopeChange(intent.value)
|
||||||
|
is ReplaceEditIntent.OnGroupChange -> onGroupChange(intent.value)
|
||||||
|
is ReplaceEditIntent.OnRegexChange -> onRegexChange(intent.value)
|
||||||
|
is ReplaceEditIntent.OnScopeTitleChange -> onScopeTitleChange(intent.value)
|
||||||
|
is ReplaceEditIntent.OnScopeContentChange -> onScopeContentChange(intent.value)
|
||||||
|
is ReplaceEditIntent.OnTimeoutChange -> onTimeoutChange(intent.value)
|
||||||
|
is ReplaceEditIntent.SetActiveField -> setActiveField(intent.field)
|
||||||
|
is ReplaceEditIntent.InsertTextAtCursor -> insertTextAtCursor(intent.text)
|
||||||
|
is ReplaceEditIntent.ToggleGroupDialog -> toggleGroupDialog(intent.show)
|
||||||
|
is ReplaceEditIntent.DeleteGroups -> deleteGroups(intent.groups)
|
||||||
|
ReplaceEditIntent.CopyRule -> copyRule()
|
||||||
|
ReplaceEditIntent.PasteRule -> pasteRule()
|
||||||
|
ReplaceEditIntent.Save -> save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun initData() {
|
private fun initData() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val id = route.id
|
val id = route.id
|
||||||
@@ -121,7 +128,7 @@ class ReplaceEditViewModel(
|
|||||||
return rule
|
return rule
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copyRule() {
|
private fun copyRule() {
|
||||||
viewModelScope.launch(Dispatchers.Main) {
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
val ruleToCopy = getReplaceRuleFromState()
|
val ruleToCopy = getReplaceRuleFromState()
|
||||||
val json = GSON.toJson(ruleToCopy)
|
val json = GSON.toJson(ruleToCopy)
|
||||||
@@ -130,7 +137,7 @@ class ReplaceEditViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pasteRule(onSuccess: () -> Unit) {
|
private fun pasteRule() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val text = app.getClipText()
|
val text = app.getClipText()
|
||||||
@@ -143,7 +150,6 @@ class ReplaceEditViewModel(
|
|||||||
|
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
updateStateFromRule(pastedRule)
|
updateStateFromRule(pastedRule)
|
||||||
onSuccess()
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
@@ -153,39 +159,44 @@ class ReplaceEditViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onNameChange(v: String) {
|
private fun onNameChange(v: String) {
|
||||||
_uiState.update { it.copy(name = v) }
|
_uiState.update { it.copy(name = v) }
|
||||||
activeField = ActiveField.Name
|
setActiveField(ActiveField.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onScopeChange(v: String) {
|
private fun onScopeChange(v: String) {
|
||||||
_uiState.update { it.copy(scope = v) }
|
_uiState.update { it.copy(scope = v) }
|
||||||
activeField = ActiveField.Scope
|
setActiveField(ActiveField.Scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPatternChange(v: String) {
|
private fun onPatternChange(v: String) {
|
||||||
_uiState.update { it.copy(pattern = v) }
|
_uiState.update { it.copy(pattern = v) }
|
||||||
activeField = ActiveField.Pattern
|
setActiveField(ActiveField.Pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onReplacementChange(v: String) {
|
private fun onReplacementChange(v: String) {
|
||||||
_uiState.update { it.copy(replacement = v) }
|
_uiState.update { it.copy(replacement = v) }
|
||||||
activeField = ActiveField.Replacement
|
setActiveField(ActiveField.Replacement)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onExcludeScopeChange(v: String) {
|
private fun onExcludeScopeChange(v: String) {
|
||||||
_uiState.update { it.copy(excludeScope = v) }
|
_uiState.update { it.copy(excludeScope = v) }
|
||||||
activeField = ActiveField.Exclude
|
setActiveField(ActiveField.Exclude)
|
||||||
}
|
}
|
||||||
fun onGroupChange(v: String) = _uiState.update { it.copy(group = v) }
|
|
||||||
fun onRegexChange(v: Boolean) = _uiState.update { it.copy(isRegex = v) }
|
private fun onGroupChange(v: String) = _uiState.update { it.copy(group = v) }
|
||||||
fun onScopeTitleChange(v: Boolean) = _uiState.update { it.copy(scopeTitle = v) }
|
private fun onRegexChange(v: Boolean) = _uiState.update { it.copy(isRegex = v) }
|
||||||
fun onScopeContentChange(v: Boolean) = _uiState.update { it.copy(scopeContent = v) }
|
private fun onScopeTitleChange(v: Boolean) = _uiState.update { it.copy(scopeTitle = v) }
|
||||||
fun onTimeoutChange(v: String) = _uiState.update { it.copy(timeout = v) }
|
private fun onScopeContentChange(v: Boolean) = _uiState.update { it.copy(scopeContent = v) }
|
||||||
fun toggleGroupDialog(show: Boolean) = _uiState.update { it.copy(showGroupDialog = show) }
|
private fun onTimeoutChange(v: String) = _uiState.update { it.copy(timeout = v) }
|
||||||
fun insertTextAtCursor(text: String) {
|
private fun toggleGroupDialog(show: Boolean) = _uiState.update { it.copy(showGroupDialog = show) }
|
||||||
val state = _uiState.value
|
|
||||||
when (activeField) {
|
private fun setActiveField(field: ActiveField) {
|
||||||
|
_uiState.update { it.copy(activeField = field) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun insertTextAtCursor(text: String) {
|
||||||
|
when (_uiState.value.activeField) {
|
||||||
ActiveField.Name -> _uiState.update { it.copy(name = it.name + text) }
|
ActiveField.Name -> _uiState.update { it.copy(name = it.name + text) }
|
||||||
ActiveField.Pattern -> _uiState.update { it.copy(pattern = it.pattern + text) }
|
ActiveField.Pattern -> _uiState.update { it.copy(pattern = it.pattern + text) }
|
||||||
ActiveField.Replacement -> _uiState.update { it.copy(replacement = it.replacement + text) }
|
ActiveField.Replacement -> _uiState.update { it.copy(replacement = it.replacement + text) }
|
||||||
@@ -195,7 +206,7 @@ class ReplaceEditViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save(onSuccess: () -> Unit) {
|
private fun save() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val state = _uiState.value
|
val state = _uiState.value
|
||||||
|
|
||||||
@@ -220,14 +231,12 @@ class ReplaceEditViewModel(
|
|||||||
|
|
||||||
replaceRuleDao.insert(rule)
|
replaceRuleDao.insert(rule)
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
_effects.tryEmit(ReplaceEditEffect.NavigateBack)
|
||||||
onSuccess()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun deleteGroups(groups: List<String>) {
|
private fun deleteGroups(groups: List<String>) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
replaceRuleDao.clearGroups(groups)
|
replaceRuleDao.clearGroups(groups)
|
||||||
toggleGroupDialog(false)
|
toggleGroupDialog(false)
|
||||||
|
|||||||
Reference in new issue
Block a user