fix: 修正规则编辑器状态与更新逻辑
This commit is contained in:
@@ -47,16 +47,16 @@ interface ReplaceRuleDao {
|
|||||||
@Query("SELECT * FROM replace_rules WHERE `group` LIKE :key OR name LIKE :key OR pattern LIKE :key OR replacement LIKE :key OR scope LIKE :key ORDER BY name COLLATE NOCASE DESC")
|
@Query("SELECT * FROM replace_rules WHERE `group` LIKE :key OR name LIKE :key OR pattern LIKE :key OR replacement LIKE :key OR scope LIKE :key ORDER BY name COLLATE NOCASE DESC")
|
||||||
fun flowSearchNameDesc(key: String): Flow<List<ReplaceRule>>
|
fun flowSearchNameDesc(key: String): Flow<List<ReplaceRule>>
|
||||||
|
|
||||||
@Query("SELECT * FROM replace_rules WHERE `group` LIKE :key ORDER BY sortOrder ASC")
|
@Query("SELECT * FROM replace_rules WHERE `group` LIKE '%' || :key || '%' ORDER BY sortOrder ASC")
|
||||||
fun flowGroupSearchAsc(key: String): Flow<List<ReplaceRule>>
|
fun flowGroupSearchAsc(key: String): Flow<List<ReplaceRule>>
|
||||||
|
|
||||||
@Query("SELECT * FROM replace_rules WHERE `group` LIKE :key ORDER BY sortOrder DESC")
|
@Query("SELECT * FROM replace_rules WHERE `group` LIKE '%' || :key || '%' ORDER BY sortOrder DESC")
|
||||||
fun flowGroupSearchDesc(key: String): Flow<List<ReplaceRule>>
|
fun flowGroupSearchDesc(key: String): Flow<List<ReplaceRule>>
|
||||||
|
|
||||||
@Query("SELECT * FROM replace_rules WHERE `group` LIKE :key ORDER BY name COLLATE NOCASE ASC")
|
@Query("SELECT * FROM replace_rules WHERE `group` LIKE '%' || :key || '%' ORDER BY name COLLATE NOCASE ASC")
|
||||||
fun flowGroupSearchNameAsc(key: String): Flow<List<ReplaceRule>>
|
fun flowGroupSearchNameAsc(key: String): Flow<List<ReplaceRule>>
|
||||||
|
|
||||||
@Query("SELECT * FROM replace_rules WHERE `group` LIKE :key ORDER BY name COLLATE NOCASE DESC")
|
@Query("SELECT * FROM replace_rules WHERE `group` LIKE '%' || :key || '%' ORDER BY name COLLATE NOCASE DESC")
|
||||||
fun flowGroupSearchNameDesc(key: String): Flow<List<ReplaceRule>>
|
fun flowGroupSearchNameDesc(key: String): Flow<List<ReplaceRule>>
|
||||||
|
|
||||||
// === 未分组 ===
|
// === 未分组 ===
|
||||||
@@ -75,7 +75,7 @@ interface ReplaceRuleDao {
|
|||||||
@Query("SELECT * FROM replace_rules where `group` like :key or name like :key or pattern like :key or replacement like :key or scope like :key ORDER BY sortOrder ASC")
|
@Query("SELECT * FROM replace_rules where `group` like :key or name like :key or pattern like :key or replacement like :key or scope like :key ORDER BY sortOrder ASC")
|
||||||
fun flowSearch(key: String): Flow<List<ReplaceRule>>
|
fun flowSearch(key: String): Flow<List<ReplaceRule>>
|
||||||
|
|
||||||
@Query("SELECT * FROM replace_rules where `group` like :key ORDER BY sortOrder ASC")
|
@Query("SELECT * FROM replace_rules where `group` like '%' || :key || '%' ORDER BY sortOrder ASC")
|
||||||
fun flowGroupSearch(key: String): Flow<List<ReplaceRule>>
|
fun flowGroupSearch(key: String): Flow<List<ReplaceRule>>
|
||||||
|
|
||||||
@Query("select `group` from replace_rules where `group` is not null and `group` <> ''")
|
@Query("select `group` from replace_rules where `group` is not null and `group` <> ''")
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ class ReplaceRuleRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun setEnabled(id: Long, enabled: Boolean) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
appDb.replaceRuleDao.updateEnabled(id, enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun insert(vararg rule: ReplaceRule) {
|
suspend fun insert(vararg rule: ReplaceRule) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
appDb.replaceRuleDao.insert(*rule)
|
appDb.replaceRuleDao.insert(*rule)
|
||||||
@@ -125,19 +131,13 @@ class ReplaceRuleRepository {
|
|||||||
suspend fun enableByIds(ids: Set<Long>) =
|
suspend fun enableByIds(ids: Set<Long>) =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
if (ids.isEmpty()) return@withContext
|
if (ids.isEmpty()) return@withContext
|
||||||
|
appDb.replaceRuleDao.updateEnabled(ids.toList(), true)
|
||||||
val rules = appDb.replaceRuleDao.getByIds(ids)
|
|
||||||
val updated = rules.map { it.copy(isEnabled = true) }
|
|
||||||
appDb.replaceRuleDao.update(*updated.toTypedArray())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun disableByIds(ids: Set<Long>) =
|
suspend fun disableByIds(ids: Set<Long>) =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
if (ids.isEmpty()) return@withContext
|
if (ids.isEmpty()) return@withContext
|
||||||
|
appDb.replaceRuleDao.updateEnabled(ids.toList(), false)
|
||||||
val rules = appDb.replaceRuleDao.getByIds(ids)
|
|
||||||
val updated = rules.map { it.copy(isEnabled = false) }
|
|
||||||
appDb.replaceRuleDao.update(*updated.toTypedArray())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteByIds(ids: Set<Long>) =
|
suspend fun deleteByIds(ids: Set<Long>) =
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package io.legado.app.ui.replace
|
|||||||
|
|
||||||
import androidx.navigation3.runtime.NavKey
|
import androidx.navigation3.runtime.NavKey
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object ReplaceRuleRoute : NavKey
|
object ReplaceRuleRoute : NavKey
|
||||||
@@ -13,5 +14,6 @@ data class ReplaceEditRoute(
|
|||||||
val isRegex: Boolean = false,
|
val isRegex: Boolean = false,
|
||||||
val scope: String? = null,
|
val scope: String? = null,
|
||||||
val isScopeTitle: Boolean = false,
|
val isScopeTitle: Boolean = false,
|
||||||
val isScopeContent: Boolean = false
|
val isScopeContent: Boolean = false,
|
||||||
|
val sessionId: String = UUID.randomUUID().toString()
|
||||||
) : NavKey
|
) : NavKey
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ class ReplaceRuleActivity : BaseComposeActivity() {
|
|||||||
|
|
||||||
entry<ReplaceEditRoute> { route ->
|
entry<ReplaceEditRoute> { route ->
|
||||||
val viewModel: ReplaceEditViewModel = koinViewModel(
|
val viewModel: ReplaceEditViewModel = koinViewModel(
|
||||||
key = "replace_edit_${System.identityHashCode(route)}"
|
key = "replace_edit_${route.sessionId}"
|
||||||
) { parametersOf(route) }
|
) { parametersOf(route) }
|
||||||
|
|
||||||
ReplaceEditRouteScreen(
|
ReplaceEditRouteScreen(
|
||||||
|
|||||||
@@ -14,14 +14,39 @@ data class ReplaceRuleItemUi(
|
|||||||
val name: String,
|
val name: String,
|
||||||
val isEnabled: Boolean,
|
val isEnabled: Boolean,
|
||||||
val group: String?,
|
val group: String?,
|
||||||
val rule: ReplaceRule
|
val pattern: String,
|
||||||
) : SelectableItem<Long>
|
val replacement: String,
|
||||||
|
val scope: String?,
|
||||||
|
val scopeTitle: Boolean,
|
||||||
|
val scopeContent: Boolean,
|
||||||
|
val excludeScope: String?,
|
||||||
|
val isRegex: Boolean,
|
||||||
|
val timeoutMillisecond: Long,
|
||||||
|
val order: Int
|
||||||
|
) : SelectableItem<Long> {
|
||||||
|
fun toEntity() = ReplaceRule(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
group = group,
|
||||||
|
pattern = pattern,
|
||||||
|
replacement = replacement,
|
||||||
|
scope = scope,
|
||||||
|
scopeTitle = scopeTitle,
|
||||||
|
scopeContent = scopeContent,
|
||||||
|
excludeScope = excludeScope,
|
||||||
|
isEnabled = isEnabled,
|
||||||
|
isRegex = isRegex,
|
||||||
|
timeoutMillisecond = timeoutMillisecond,
|
||||||
|
order = order
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
data class ReplaceRuleUiState(
|
data class ReplaceRuleUiState(
|
||||||
override val items: List<ReplaceRuleItemUi> = emptyList(),
|
override val items: List<ReplaceRuleItemUi> = emptyList(),
|
||||||
override val selectedIds: Set<Long> = emptySet(),
|
override val selectedIds: Set<Long> = emptySet(),
|
||||||
override val searchKey: String = "",
|
override val searchKey: String = "",
|
||||||
val sortMode: String = "desc",
|
val sortMode: String = "desc",
|
||||||
|
val selectedGroup: String? = null,
|
||||||
val interaction: InteractionState = InteractionState()
|
val interaction: InteractionState = InteractionState()
|
||||||
) : ListUiState<ReplaceRuleItemUi> {
|
) : ListUiState<ReplaceRuleItemUi> {
|
||||||
override val isSearch: Boolean get() = interaction.isSearchMode
|
override val isSearch: Boolean get() = interaction.isSearchMode
|
||||||
@@ -44,7 +69,7 @@ sealed interface ReplaceRuleIntent {
|
|||||||
data class MoveItem(val from: Int, val to: Int) : ReplaceRuleIntent
|
data class MoveItem(val from: Int, val to: Int) : ReplaceRuleIntent
|
||||||
data object SaveSortOrder : ReplaceRuleIntent
|
data object SaveSortOrder : ReplaceRuleIntent
|
||||||
data class DeleteRule(val rule: ReplaceRule) : ReplaceRuleIntent
|
data class DeleteRule(val rule: ReplaceRule) : ReplaceRuleIntent
|
||||||
data class SetRuleEnabled(val rule: ReplaceRule, val enabled: Boolean) : ReplaceRuleIntent
|
data class SetRuleEnabled(val id: Long, val enabled: Boolean) : ReplaceRuleIntent
|
||||||
data class CopyRule(val rule: ReplaceRule) : ReplaceRuleIntent
|
data class CopyRule(val rule: ReplaceRule) : ReplaceRuleIntent
|
||||||
data class ImportSource(val text: String) : ReplaceRuleIntent
|
data class ImportSource(val text: String) : ReplaceRuleIntent
|
||||||
data object CancelImport : ReplaceRuleIntent
|
data object CancelImport : ReplaceRuleIntent
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import androidx.compose.material3.animateFloatingActionButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -127,8 +126,11 @@ 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) }
|
||||||
|
|
||||||
var selectedTabIndex by remember { mutableIntStateOf(0) }
|
|
||||||
val tabItems = remember(groups) { listOf("全部") + groups }
|
val tabItems = remember(groups) { listOf("全部") + groups }
|
||||||
|
val selectedTabIndex = state.selectedGroup
|
||||||
|
?.let(tabItems::indexOf)
|
||||||
|
?.takeIf { it >= 0 }
|
||||||
|
?: 0
|
||||||
|
|
||||||
val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
|
val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
|
||||||
onIntent(ReplaceRuleIntent.MoveItem(from.index, to.index))
|
onIntent(ReplaceRuleIntent.MoveItem(from.index, to.index))
|
||||||
@@ -231,10 +233,8 @@ fun ReplaceRuleScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(groups) {
|
LaunchedEffect(groups, state.selectedGroup) {
|
||||||
val maxIndex = groups.size
|
if (state.selectedGroup != null && state.selectedGroup !in groups) {
|
||||||
if (selectedTabIndex > maxIndex) {
|
|
||||||
selectedTabIndex = 0
|
|
||||||
onIntent(ReplaceRuleIntent.SetGroup("全部"))
|
onIntent(ReplaceRuleIntent.SetGroup("全部"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,7 +331,6 @@ fun ReplaceRuleScreen(
|
|||||||
tabTitles = tabItems,
|
tabTitles = tabItems,
|
||||||
selectedTabIndex = selectedTabIndex,
|
selectedTabIndex = selectedTabIndex,
|
||||||
onTabSelected = { index ->
|
onTabSelected = { index ->
|
||||||
selectedTabIndex = index
|
|
||||||
onIntent(ReplaceRuleIntent.SetGroup(tabItems[index]))
|
onIntent(ReplaceRuleIntent.SetGroup(tabItems[index]))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -422,13 +421,13 @@ fun ReplaceRuleScreen(
|
|||||||
onIntent(ReplaceRuleIntent.ToggleSelection(ui.id))
|
onIntent(ReplaceRuleIntent.ToggleSelection(ui.id))
|
||||||
},
|
},
|
||||||
onEnabledChange = { enabled ->
|
onEnabledChange = { enabled ->
|
||||||
onIntent(ReplaceRuleIntent.SetRuleEnabled(ui.rule, enabled))
|
onIntent(ReplaceRuleIntent.SetRuleEnabled(ui.id, enabled))
|
||||||
},
|
},
|
||||||
onClickEdit = {
|
onClickEdit = {
|
||||||
onNavigateToEdit(
|
onNavigateToEdit(
|
||||||
ReplaceEditRoute(
|
ReplaceEditRoute(
|
||||||
id = ui.id,
|
id = ui.id,
|
||||||
pattern = ui.rule.pattern
|
pattern = ui.pattern
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -436,15 +435,24 @@ fun ReplaceRuleScreen(
|
|||||||
dropdownContent = { dismiss ->
|
dropdownContent = { dismiss ->
|
||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = stringResource(R.string.move_to_top),
|
text = stringResource(R.string.move_to_top),
|
||||||
onClick = { onIntent(ReplaceRuleIntent.ToTop(ui.rule)); dismiss() }
|
onClick = {
|
||||||
|
onIntent(ReplaceRuleIntent.ToTop(ui.toEntity()))
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = stringResource(R.string.move_to_bottom),
|
text = stringResource(R.string.move_to_bottom),
|
||||||
onClick = { onIntent(ReplaceRuleIntent.ToBottom(ui.rule)); dismiss() }
|
onClick = {
|
||||||
|
onIntent(ReplaceRuleIntent.ToBottom(ui.toEntity()))
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = stringResource(R.string.delete),
|
text = stringResource(R.string.delete),
|
||||||
onClick = { showDeleteRuleDialog = ui.rule; dismiss() }
|
onClick = {
|
||||||
|
showDeleteRuleDialog = ui.toEntity()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class ReplaceRuleViewModel(
|
|||||||
is ReplaceRuleIntent.MoveItem -> moveItemInList(intent.from, intent.to)
|
is ReplaceRuleIntent.MoveItem -> moveItemInList(intent.from, intent.to)
|
||||||
ReplaceRuleIntent.SaveSortOrder -> saveSortOrder()
|
ReplaceRuleIntent.SaveSortOrder -> saveSortOrder()
|
||||||
is ReplaceRuleIntent.DeleteRule -> delete(intent.rule)
|
is ReplaceRuleIntent.DeleteRule -> delete(intent.rule)
|
||||||
is ReplaceRuleIntent.SetRuleEnabled -> update(intent.rule.copy(isEnabled = intent.enabled))
|
is ReplaceRuleIntent.SetRuleEnabled -> setEnabled(intent.id, intent.enabled)
|
||||||
is ReplaceRuleIntent.CopyRule -> { /* not implemented for ReplaceRule */ }
|
is ReplaceRuleIntent.CopyRule -> { /* not implemented for ReplaceRule */ }
|
||||||
is ReplaceRuleIntent.ImportSource -> importSource(intent.text)
|
is ReplaceRuleIntent.ImportSource -> importSource(intent.text)
|
||||||
ReplaceRuleIntent.CancelImport -> cancelImport()
|
ReplaceRuleIntent.CancelImport -> cancelImport()
|
||||||
@@ -160,6 +160,7 @@ class ReplaceRuleViewModel(
|
|||||||
selectedIds = selectedIds,
|
selectedIds = selectedIds,
|
||||||
searchKey = _searchKey.value,
|
searchKey = _searchKey.value,
|
||||||
sortMode = _sortMode.value,
|
sortMode = _sortMode.value,
|
||||||
|
selectedGroup = _group.value,
|
||||||
interaction = InteractionState(
|
interaction = InteractionState(
|
||||||
isSearchMode = isSearch,
|
isSearchMode = isSearch,
|
||||||
isUploading = isUploading || (importState is BaseImportUiState.Loading),
|
isUploading = isUploading || (importState is BaseImportUiState.Loading),
|
||||||
@@ -168,8 +169,23 @@ class ReplaceRuleViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ReplaceRule.toUiItem() = ReplaceRuleItemUi(id, name, isEnabled, group, this)
|
override fun ReplaceRule.toUiItem() = ReplaceRuleItemUi(
|
||||||
override fun ruleItemToEntity(item: ReplaceRuleItemUi): ReplaceRule = item.rule
|
id = id,
|
||||||
|
name = name,
|
||||||
|
isEnabled = isEnabled,
|
||||||
|
group = group,
|
||||||
|
pattern = pattern,
|
||||||
|
replacement = replacement,
|
||||||
|
scope = scope,
|
||||||
|
scopeTitle = scopeTitle,
|
||||||
|
scopeContent = scopeContent,
|
||||||
|
excludeScope = excludeScope,
|
||||||
|
isRegex = isRegex,
|
||||||
|
timeoutMillisecond = timeoutMillisecond,
|
||||||
|
order = order
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun ruleItemToEntity(item: ReplaceRuleItemUi): ReplaceRule = item.toEntity()
|
||||||
|
|
||||||
override suspend fun generateJson(entities: List<ReplaceRule>): String = GSON.toJson(entities)
|
override suspend fun generateJson(entities: List<ReplaceRule>): String = GSON.toJson(entities)
|
||||||
|
|
||||||
@@ -248,13 +264,15 @@ class ReplaceRuleViewModel(
|
|||||||
private 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.toEntity() }, _sortMode.value == "desc")
|
||||||
_localItems.value = null
|
_localItems.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun update(vararg rule: ReplaceRule) = viewModelScope.launch { repository.update(*rule) }
|
private fun setEnabled(id: Long, enabled: Boolean) =
|
||||||
|
viewModelScope.launch { repository.setEnabled(id, enabled) }
|
||||||
|
|
||||||
private 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>) =
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
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 androidx.compose.ui.zIndex
|
||||||
@@ -110,6 +111,11 @@ fun ReplaceEditScreen(
|
|||||||
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()
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
|
val onSave = {
|
||||||
|
focusManager.clearFocus(force = true)
|
||||||
|
onIntent(ReplaceEditIntent.Save)
|
||||||
|
}
|
||||||
|
|
||||||
AppScaffold(
|
AppScaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -127,7 +133,7 @@ fun ReplaceEditScreen(
|
|||||||
exit = fadeOut()
|
exit = fadeOut()
|
||||||
) {
|
) {
|
||||||
TopBarActionButton(
|
TopBarActionButton(
|
||||||
onClick = { onIntent(ReplaceEditIntent.Save) },
|
onClick = onSave,
|
||||||
imageVector = Icons.Default.Save,
|
imageVector = Icons.Default.Save,
|
||||||
contentDescription = stringResource(R.string.action_save)
|
contentDescription = stringResource(R.string.action_save)
|
||||||
)
|
)
|
||||||
@@ -168,7 +174,7 @@ fun ReplaceEditScreen(
|
|||||||
visible = !isKeyboardVisible,
|
visible = !isKeyboardVisible,
|
||||||
alignment = Alignment.BottomEnd,
|
alignment = Alignment.BottomEnd,
|
||||||
),
|
),
|
||||||
onClick = { onIntent(ReplaceEditIntent.Save) },
|
onClick = onSave,
|
||||||
tooltipText = stringResource(R.string.action_save),
|
tooltipText = stringResource(R.string.action_save),
|
||||||
icon = Icons.Default.Save
|
icon = Icons.Default.Save
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package io.legado.app.ui.replace
|
||||||
|
|
||||||
|
import org.junit.Assert.assertNotEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ReplaceRuleStateTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun editedRuleContentChangesListItemEquality() {
|
||||||
|
val item = ReplaceRuleItemUi(
|
||||||
|
id = 1L,
|
||||||
|
name = "rule",
|
||||||
|
isEnabled = true,
|
||||||
|
group = null,
|
||||||
|
pattern = "before",
|
||||||
|
replacement = "",
|
||||||
|
scope = null,
|
||||||
|
scopeTitle = false,
|
||||||
|
scopeContent = true,
|
||||||
|
excludeScope = null,
|
||||||
|
isRegex = true,
|
||||||
|
timeoutMillisecond = 3000L,
|
||||||
|
order = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
assertNotEquals(item, item.copy(pattern = "after"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun newEditRoutesUseDifferentSessions() {
|
||||||
|
assertNotEquals(ReplaceEditRoute(), ReplaceEditRoute())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user