[修复] 替换净化编辑或修改时无法正确更新正文的问题 #583
[新增] 替换净化编辑与管理界面使用Navigation处理导航逻辑与优化动画
This commit is contained in:
@@ -6,6 +6,7 @@ plugins {
|
|||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.kotlin.parcelize)
|
alias(libs.plugins.kotlin.parcelize)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
alias(libs.plugins.room)
|
alias(libs.plugins.room)
|
||||||
alias(libs.plugins.ksp)
|
alias(libs.plugins.ksp)
|
||||||
alias(libs.plugins.google.services)
|
alias(libs.plugins.google.services)
|
||||||
@@ -181,6 +182,7 @@ dependencies {
|
|||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.bundles.androidTest)
|
androidTestImplementation(libs.bundles.androidTest)
|
||||||
implementation(libs.kotlin.stdlib)
|
implementation(libs.kotlin.stdlib)
|
||||||
|
implementation(libs.kotlinx.serialization.json)
|
||||||
implementation(libs.bundles.coroutines)
|
implementation(libs.bundles.coroutines)
|
||||||
implementation(libs.core.ktx)
|
implementation(libs.core.ktx)
|
||||||
implementation(libs.appcompat.appcompat)
|
implementation(libs.appcompat.appcompat)
|
||||||
@@ -256,6 +258,11 @@ dependencies {
|
|||||||
implementation(libs.androidx.compose.foundation)
|
implementation(libs.androidx.compose.foundation)
|
||||||
implementation(libs.androidx.constraintlayout.compose)
|
implementation(libs.androidx.constraintlayout.compose)
|
||||||
implementation(libs.androidx.compose.ui.viewbinding)
|
implementation(libs.androidx.compose.ui.viewbinding)
|
||||||
|
implementation(libs.androidx.navigation3.runtime)
|
||||||
|
implementation(libs.androidx.navigation3.ui)
|
||||||
|
implementation(libs.androidx.compose.adaptive)
|
||||||
|
implementation(libs.androidx.compose.adaptive.layout)
|
||||||
|
implementation(libs.androidx.compose.adaptive.navigation)
|
||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation(libs.androidx.compose.material)
|
implementation(libs.androidx.compose.material)
|
||||||
implementation(libs.compose.materialIcons)
|
implementation(libs.compose.materialIcons)
|
||||||
|
|||||||
@@ -211,13 +211,6 @@
|
|||||||
android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
|
android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||||
<!-- 替换规则编辑 -->
|
|
||||||
<activity
|
|
||||||
android:name=".ui.replace.edit.ReplaceEditActivity"
|
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
|
||||||
<!-- 配置界面 -->
|
<!-- 配置界面 -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.config.ConfigActivity"
|
android:name=".ui.config.ConfigActivity"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import io.legado.app.ui.book.explore.ExploreShowViewModel
|
|||||||
import io.legado.app.ui.book.readRecord.ReadRecordViewModel
|
import io.legado.app.ui.book.readRecord.ReadRecordViewModel
|
||||||
import io.legado.app.ui.book.searchContent.SearchContentViewModel
|
import io.legado.app.ui.book.searchContent.SearchContentViewModel
|
||||||
import io.legado.app.ui.main.my.MyViewModel
|
import io.legado.app.ui.main.my.MyViewModel
|
||||||
|
import io.legado.app.ui.replace.ReplaceEditRoute
|
||||||
import io.legado.app.ui.replace.ReplaceRuleViewModel
|
import io.legado.app.ui.replace.ReplaceRuleViewModel
|
||||||
import io.legado.app.ui.replace.edit.ReplaceEditViewModel
|
import io.legado.app.ui.replace.edit.ReplaceEditViewModel
|
||||||
import org.koin.android.ext.koin.androidApplication
|
import org.koin.android.ext.koin.androidApplication
|
||||||
@@ -34,7 +35,13 @@ val appModule = module {
|
|||||||
single { SearchContentRepository() }
|
single { SearchContentRepository() }
|
||||||
|
|
||||||
viewModel { ReadRecordViewModel(get(), get()) }
|
viewModel { ReadRecordViewModel(get(), get()) }
|
||||||
viewModel { ReplaceEditViewModel(get(), get(), get()) }
|
viewModel { (route: ReplaceEditRoute) ->
|
||||||
|
ReplaceEditViewModel(
|
||||||
|
app = get(),
|
||||||
|
replaceRuleDao = get(),
|
||||||
|
savedStateHandle = get()
|
||||||
|
)
|
||||||
|
}
|
||||||
viewModel { ReplaceRuleViewModel(androidApplication()) }
|
viewModel { ReplaceRuleViewModel(androidApplication()) }
|
||||||
viewModel { ExploreShowViewModel(get()) }
|
viewModel { ExploreShowViewModel(get()) }
|
||||||
viewModel { SearchContentViewModel(get(), get()) }
|
viewModel { SearchContentViewModel(get(), get()) }
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ import io.legado.app.help.config.AppConfig
|
|||||||
import io.legado.app.lib.dialogs.alert
|
import io.legado.app.lib.dialogs.alert
|
||||||
//import io.legado.app.lib.theme.primaryColor
|
//import io.legado.app.lib.theme.primaryColor
|
||||||
import io.legado.app.model.ReadBook
|
import io.legado.app.model.ReadBook
|
||||||
import io.legado.app.ui.replace.edit.ReplaceEditActivity
|
import io.legado.app.ui.replace.ReplaceEditRoute
|
||||||
|
import io.legado.app.ui.replace.ReplaceRuleActivity
|
||||||
import io.legado.app.utils.setLayout
|
import io.legado.app.utils.setLayout
|
||||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||||
|
|
||||||
@@ -95,7 +96,8 @@ class EffectiveReplacesDialog : BaseDialogFragment(R.layout.dialog_recycler_view
|
|||||||
showChineseConvertAlert()
|
showChineseConvertAlert()
|
||||||
return@let
|
return@let
|
||||||
}
|
}
|
||||||
editActivity.launch(ReplaceEditActivity.startIntent(requireContext(), item.id))
|
val editRoute = ReplaceEditRoute(id = item.id, pattern = item.pattern)
|
||||||
|
ReplaceRuleActivity.startIntent(requireContext(), editRoute)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,8 +102,8 @@ import io.legado.app.ui.book.toc.rule.TxtTocRuleDialog
|
|||||||
import io.legado.app.ui.browser.WebViewActivity
|
import io.legado.app.ui.browser.WebViewActivity
|
||||||
import io.legado.app.ui.dict.DictDialog
|
import io.legado.app.ui.dict.DictDialog
|
||||||
import io.legado.app.ui.login.SourceLoginActivity
|
import io.legado.app.ui.login.SourceLoginActivity
|
||||||
|
import io.legado.app.ui.replace.ReplaceEditRoute
|
||||||
import io.legado.app.ui.replace.ReplaceRuleActivity
|
import io.legado.app.ui.replace.ReplaceRuleActivity
|
||||||
import io.legado.app.ui.replace.edit.ReplaceEditActivity
|
|
||||||
import io.legado.app.ui.widget.PopupAction
|
import io.legado.app.ui.widget.PopupAction
|
||||||
import io.legado.app.ui.widget.dialog.PhotoDialog
|
import io.legado.app.ui.widget.dialog.PhotoDialog
|
||||||
import io.legado.app.utils.Debounce
|
import io.legado.app.utils.Debounce
|
||||||
@@ -981,20 +981,23 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
|
|
||||||
R.id.menu_replace -> {
|
R.id.menu_replace -> {
|
||||||
val scopes = arrayListOf<String>()
|
val scopes = arrayListOf<String>()
|
||||||
ReadBook.book?.name?.let {
|
ReadBook.book?.name?.let { scopes.add(it) }
|
||||||
scopes.add(it)
|
ReadBook.bookSource?.bookSourceUrl?.let { scopes.add(it) }
|
||||||
}
|
|
||||||
ReadBook.bookSource?.bookSourceUrl?.let {
|
|
||||||
scopes.add(it)
|
|
||||||
}
|
|
||||||
val text = selectedText.lineSequence().map { it.trim() }.joinToString("\n")
|
val text = selectedText.lineSequence().map { it.trim() }.joinToString("\n")
|
||||||
|
|
||||||
|
val editRoute = ReplaceEditRoute(
|
||||||
|
id = -1,
|
||||||
|
pattern = text,
|
||||||
|
scope = scopes.joinToString(";"),
|
||||||
|
isScopeTitle = false,
|
||||||
|
isScopeContent = true
|
||||||
|
)
|
||||||
|
|
||||||
replaceActivity.launch(
|
replaceActivity.launch(
|
||||||
ReplaceEditActivity.startIntent(
|
ReplaceRuleActivity.startIntent(
|
||||||
this,
|
context = this,
|
||||||
pattern = text,
|
editRoute = editRoute
|
||||||
scope = scopes.joinToString(";"),
|
|
||||||
isScopeTitle = false,
|
|
||||||
isScopeContent = true
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -14,13 +14,11 @@ import android.text.style.ForegroundColorSpan
|
|||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentPagerAdapter
|
import androidx.fragment.app.FragmentPagerAdapter
|
||||||
@@ -29,7 +27,6 @@ import io.legado.app.R
|
|||||||
import io.legado.app.base.VMBaseActivity
|
import io.legado.app.base.VMBaseActivity
|
||||||
import io.legado.app.constant.EventBus
|
import io.legado.app.constant.EventBus
|
||||||
import io.legado.app.data.entities.Book
|
import io.legado.app.data.entities.Book
|
||||||
import io.legado.app.data.entities.BookChapter
|
|
||||||
import io.legado.app.databinding.ActivityChapterListBinding
|
import io.legado.app.databinding.ActivityChapterListBinding
|
||||||
import io.legado.app.databinding.DialogDownloadChoiceBinding
|
import io.legado.app.databinding.DialogDownloadChoiceBinding
|
||||||
import io.legado.app.help.book.isLocal
|
import io.legado.app.help.book.isLocal
|
||||||
@@ -41,15 +38,11 @@ import io.legado.app.model.ReadBook
|
|||||||
import io.legado.app.ui.about.AppLogDialog
|
import io.legado.app.ui.about.AppLogDialog
|
||||||
import io.legado.app.ui.book.toc.rule.TxtTocRuleDialog
|
import io.legado.app.ui.book.toc.rule.TxtTocRuleDialog
|
||||||
import io.legado.app.ui.file.HandleFileContract
|
import io.legado.app.ui.file.HandleFileContract
|
||||||
|
import io.legado.app.ui.replace.ReplaceEditRoute
|
||||||
import io.legado.app.ui.replace.ReplaceRuleActivity
|
import io.legado.app.ui.replace.ReplaceRuleActivity
|
||||||
import io.legado.app.ui.replace.edit.ReplaceEditActivity
|
|
||||||
import io.legado.app.ui.widget.dialog.WaitDialog
|
import io.legado.app.ui.widget.dialog.WaitDialog
|
||||||
import io.legado.app.utils.applyTint
|
|
||||||
import io.legado.app.utils.dpToPx
|
|
||||||
import io.legado.app.utils.gone
|
import io.legado.app.utils.gone
|
||||||
import io.legado.app.utils.hideSoftInput
|
|
||||||
import io.legado.app.utils.observeEvent
|
import io.legado.app.utils.observeEvent
|
||||||
import io.legado.app.utils.shouldHideSoftInput
|
|
||||||
import io.legado.app.utils.showDialogFragment
|
import io.legado.app.utils.showDialogFragment
|
||||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||||
import io.legado.app.utils.visible
|
import io.legado.app.utils.visible
|
||||||
@@ -262,14 +255,17 @@ class TocActivity : VMBaseActivity<ActivityChapterListBinding, TocViewModel>(),
|
|||||||
val scopes = arrayListOf<String>()
|
val scopes = arrayListOf<String>()
|
||||||
viewModel.bookData.value?.name?.let { scopes.add(it) }
|
viewModel.bookData.value?.name?.let { scopes.add(it) }
|
||||||
viewModel.bookSource?.bookSourceUrl?.let { scopes.add(it) }
|
viewModel.bookSource?.bookSourceUrl?.let { scopes.add(it) }
|
||||||
|
|
||||||
|
val editRoute = ReplaceEditRoute(
|
||||||
|
id = -1,
|
||||||
|
pattern = "text",
|
||||||
|
scope = scopes.joinToString(";"),
|
||||||
|
isScopeTitle = true,
|
||||||
|
isScopeContent = false
|
||||||
|
)
|
||||||
|
|
||||||
replaceActivity.launch(
|
replaceActivity.launch(
|
||||||
ReplaceEditActivity.startIntent(
|
ReplaceRuleActivity.startIntent(this, editRoute)
|
||||||
this,
|
|
||||||
pattern = "text",
|
|
||||||
scope = scopes.joinToString(";"),
|
|
||||||
isScopeTitle = true,
|
|
||||||
isScopeContent = false
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package io.legado.app.ui.replace
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
object ReplaceRuleRoute
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ReplaceEditRoute(
|
||||||
|
val id: Long = -1,
|
||||||
|
val pattern: String? = null,
|
||||||
|
val isRegex: Boolean = false,
|
||||||
|
val scope: String? = null,
|
||||||
|
val isScopeTitle: Boolean = false,
|
||||||
|
val isScopeContent: Boolean = false
|
||||||
|
)
|
||||||
@@ -1,23 +1,95 @@
|
|||||||
package io.legado.app.ui.replace
|
package io.legado.app.ui.replace
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.content.Context
|
||||||
import androidx.activity.compose.setContent
|
import android.content.Intent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.activity.compose.LocalActivity
|
||||||
|
import androidx.compose.animation.SharedTransitionLayout
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import io.legado.app.ui.theme.AppTheme
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import androidx.navigation.toRoute
|
||||||
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.ReplaceEditViewModel
|
||||||
|
import io.legado.app.ui.theme.AppTheme
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
import org.koin.core.parameter.parametersOf
|
||||||
|
|
||||||
class ReplaceRuleActivity : BaseComposeActivity() {
|
class ReplaceRuleActivity : BaseComposeActivity() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_START_ROUTE = "start_route"
|
||||||
|
fun startIntent(
|
||||||
|
context: Context,
|
||||||
|
editRoute: ReplaceEditRoute? = null
|
||||||
|
): Intent = Intent(context, ReplaceRuleActivity::class.java).apply {
|
||||||
|
editRoute?.let {
|
||||||
|
putExtra(EXTRA_START_ROUTE, Json.encodeToString(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
ReplaceRuleScreen(onBackClick = { finish() })
|
val navController = rememberNavController()
|
||||||
|
val context = LocalActivity.current
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
context?.setResult(RESULT_OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
val startRouteJson = intent.getStringExtra(EXTRA_START_ROUTE)
|
||||||
|
val initialEditRoute = remember(startRouteJson) {
|
||||||
|
startRouteJson?.let { Json.decodeFromString<ReplaceEditRoute>(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val startDestination: Any = initialEditRoute ?: ReplaceRuleRoute
|
||||||
|
|
||||||
|
SharedTransitionLayout {
|
||||||
|
NavHost(
|
||||||
|
navController = navController,
|
||||||
|
startDestination = startDestination
|
||||||
|
) {
|
||||||
|
composable<ReplaceRuleRoute> {
|
||||||
|
ReplaceRuleScreen(
|
||||||
|
sharedTransitionScope = this@SharedTransitionLayout,
|
||||||
|
animatedVisibilityScope = this@composable,
|
||||||
|
onBackClick = { finish() },
|
||||||
|
onNavigateToEdit = { route -> navController.navigate(route) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<ReplaceEditRoute> { backStackEntry ->
|
||||||
|
val route = backStackEntry.toRoute<ReplaceEditRoute>()
|
||||||
|
val viewModel: ReplaceEditViewModel = koinViewModel { parametersOf(route) }
|
||||||
|
|
||||||
|
ReplaceEditScreen(
|
||||||
|
sharedTransitionScope = this@SharedTransitionLayout,
|
||||||
|
animatedVisibilityScope = this@composable,
|
||||||
|
viewModel = viewModel,
|
||||||
|
onBack = {
|
||||||
|
// 如果编辑页是唯一的页面,点击返回应关闭 Activity
|
||||||
|
if (navController.previousBackStackEntry == null) {
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSaveSuccess = {
|
||||||
|
if (navController.previousBackStackEntry == null) finish()
|
||||||
|
else navController.popBackStack()
|
||||||
|
},
|
||||||
|
onShowHelp = { /* ... */ }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import android.content.ClipData
|
|||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
@@ -86,7 +88,6 @@ import com.google.gson.Gson
|
|||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.data.entities.ReplaceRule
|
import io.legado.app.data.entities.ReplaceRule
|
||||||
import io.legado.app.data.repository.UploadRepository
|
import io.legado.app.data.repository.UploadRepository
|
||||||
import io.legado.app.ui.replace.edit.ReplaceEditActivity
|
|
||||||
import io.legado.app.ui.widget.components.ActionItem
|
import io.legado.app.ui.widget.components.ActionItem
|
||||||
import io.legado.app.ui.widget.components.AnimatedText
|
import io.legado.app.ui.widget.components.AnimatedText
|
||||||
import io.legado.app.ui.widget.components.DraggableSelectionHandler
|
import io.legado.app.ui.widget.components.DraggableSelectionHandler
|
||||||
@@ -111,8 +112,11 @@ import sh.calvin.reorderable.rememberReorderableLazyListState
|
|||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReplaceRuleScreen(
|
fun ReplaceRuleScreen(
|
||||||
|
sharedTransitionScope: SharedTransitionScope,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope,
|
||||||
viewModel: ReplaceRuleViewModel = koinViewModel(),
|
viewModel: ReplaceRuleViewModel = koinViewModel(),
|
||||||
onBackClick: () -> Unit
|
onBackClick: () -> Unit,
|
||||||
|
onNavigateToEdit: (ReplaceEditRoute) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val uploadRepository: UploadRepository = koinInject()
|
val uploadRepository: UploadRepository = koinInject()
|
||||||
@@ -519,16 +523,24 @@ fun ReplaceRuleScreen(
|
|||||||
tooltip = { PlainTooltip { Text("Localized description") } },
|
tooltip = { PlainTooltip { Text("Localized description") } },
|
||||||
state = rememberTooltipState(),
|
state = rememberTooltipState(),
|
||||||
) {
|
) {
|
||||||
FloatingActionButton(
|
with(sharedTransitionScope) {
|
||||||
modifier = Modifier.animateFloatingActionButton(
|
FloatingActionButton(
|
||||||
visible = !inSelectionMode,
|
modifier = Modifier
|
||||||
alignment = Alignment.BottomEnd,
|
.animateFloatingActionButton(
|
||||||
),
|
visible = !inSelectionMode,
|
||||||
onClick = {
|
alignment = Alignment.BottomEnd,
|
||||||
context.startActivity(ReplaceEditActivity.startIntent(context))
|
)
|
||||||
|
.sharedBounds(
|
||||||
|
sharedContentState = rememberSharedContentState(key = "fab_add"),
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
|
resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds
|
||||||
|
),
|
||||||
|
onClick = {
|
||||||
|
onNavigateToEdit(ReplaceEditRoute(id = -1))
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Add, contentDescription = "Add Rule")
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
Icon(Icons.Default.Add, contentDescription = "Add Rule")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -555,44 +567,53 @@ fun ReplaceRuleScreen(
|
|||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
items(rules, key = { it.id }) { ui ->
|
items(rules, key = { it.id }) { ui ->
|
||||||
ReorderableSelectionItem(
|
with(sharedTransitionScope) {
|
||||||
state = reorderableState,
|
ReorderableSelectionItem(
|
||||||
key = ui.id,
|
state = reorderableState,
|
||||||
title = ui.name,
|
key = ui.id,
|
||||||
isEnabled = ui.isEnabled,
|
title = ui.name,
|
||||||
isSelected = selectedRuleIds.contains(ui.id),
|
isEnabled = ui.isEnabled,
|
||||||
inSelectionMode = inSelectionMode,
|
isSelected = selectedRuleIds.contains(ui.id),
|
||||||
canReorder = canReorder,
|
inSelectionMode = inSelectionMode,
|
||||||
onToggleSelection = {
|
canReorder = canReorder,
|
||||||
viewModel.toggleSelection(ui.id)
|
onToggleSelection = {
|
||||||
},
|
viewModel.toggleSelection(ui.id)
|
||||||
onEnabledChange = { enabled ->
|
},
|
||||||
viewModel.update(ui.rule.copy(isEnabled = enabled))
|
onEnabledChange = { enabled ->
|
||||||
},
|
viewModel.update(ui.rule.copy(isEnabled = enabled))
|
||||||
onClickEdit = {
|
},
|
||||||
context.startActivity(
|
onClickEdit = {
|
||||||
ReplaceEditActivity.startIntent(
|
onNavigateToEdit(
|
||||||
context,
|
ReplaceEditRoute(
|
||||||
ui.id
|
id = ui.id,
|
||||||
|
pattern = ui.rule.pattern
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
},
|
||||||
},
|
modifier = Modifier
|
||||||
modifier = Modifier.padding(horizontal = 12.dp),
|
.padding(horizontal = 12.dp)
|
||||||
dropdownContent = { dismiss ->
|
.sharedBounds(
|
||||||
DropdownMenuItem(
|
sharedContentState = rememberSharedContentState(key = "rule_${ui.id}"),
|
||||||
text = { Text("移至顶部") },
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
onClick = { viewModel.toTop(ui.rule); dismiss() }
|
resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds,
|
||||||
)
|
//clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(12.dp))
|
||||||
DropdownMenuItem(
|
),
|
||||||
text = { Text("移至底部") },
|
dropdownContent = { dismiss ->
|
||||||
onClick = { viewModel.toBottom(ui.rule); dismiss() }
|
DropdownMenuItem(
|
||||||
)
|
text = { Text("移至顶部") },
|
||||||
DropdownMenuItem(
|
onClick = { viewModel.toTop(ui.rule); dismiss() }
|
||||||
text = { Text("删除") },
|
)
|
||||||
onClick = { showDeleteRuleDialog = ui.rule; dismiss() }
|
DropdownMenuItem(
|
||||||
)
|
text = { Text("移至底部") },
|
||||||
}
|
onClick = { viewModel.toBottom(ui.rule); dismiss() }
|
||||||
)
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text("删除") },
|
||||||
|
onClick = { showDeleteRuleDialog = ui.rule; dismiss() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (inSelectionMode) {
|
if (inSelectionMode) {
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package io.legado.app.ui.replace.edit
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import io.legado.app.ui.theme.AppTheme
|
|
||||||
import io.legado.app.base.BaseComposeActivity
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 编辑替换规则
|
|
||||||
*/
|
|
||||||
class ReplaceEditActivity : BaseComposeActivity() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun startIntent(
|
|
||||||
context: Context,
|
|
||||||
id: Long = -1,
|
|
||||||
pattern: String? = null,
|
|
||||||
isRegex: Boolean = false,
|
|
||||||
scope: String? = null,
|
|
||||||
isScopeTitle: Boolean = false,
|
|
||||||
isScopeContent: Boolean = false
|
|
||||||
): Intent = Intent(context, ReplaceEditActivity::class.java).apply {
|
|
||||||
putExtra("id", id)
|
|
||||||
putExtra("pattern", pattern)
|
|
||||||
putExtra("isRegex", isRegex)
|
|
||||||
putExtra("scope", scope)
|
|
||||||
putExtra("isScopeTitle", isScopeTitle)
|
|
||||||
putExtra("isScopeContent", isScopeContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun Content() {
|
|
||||||
setContent {
|
|
||||||
AppTheme {
|
|
||||||
ReplaceEditScreen(
|
|
||||||
onBack = { finish() },
|
|
||||||
onSaveSuccess = {
|
|
||||||
setResult(RESULT_OK)
|
|
||||||
finish()
|
|
||||||
},
|
|
||||||
onShowHelp = { key -> showHelp(key) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showHelp(key: String) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package io.legado.app.ui.replace.edit
|
package io.legado.app.ui.replace.edit
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -82,225 +84,267 @@ fun keyboardAsState(): State<Boolean> {
|
|||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReplaceEditScreen(
|
fun ReplaceEditScreen(
|
||||||
|
sharedTransitionScope: SharedTransitionScope,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onSaveSuccess: () -> Unit,
|
onSaveSuccess: () -> Unit,
|
||||||
onShowHelp: (String) -> Unit,
|
onShowHelp: (String) -> Unit,
|
||||||
viewModel: ReplaceEditViewModel = koinViewModel()
|
viewModel: ReplaceEditViewModel = koinViewModel()
|
||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsState()
|
val state by viewModel.uiState.collectAsState()
|
||||||
|
val sharedKey = remember(state.id) {
|
||||||
|
if (state.id > 0) "rule_${state.id}" else "fab_add"
|
||||||
|
}
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
val isKeyboardVisible by keyboardAsState()
|
val isKeyboardVisible by keyboardAsState()
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
Scaffold(
|
|
||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
|
||||||
topBar = {
|
|
||||||
GlassMediumFlexibleTopAppBar(
|
|
||||||
title = { Text(if (state.id > 0) "编辑替换规则" else "新增替换规则") },
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back") }
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
|
|
||||||
IconButton(onClick = { onShowHelp("regexHelp") }) {
|
with(sharedTransitionScope) {
|
||||||
Icon(Icons.AutoMirrored.Filled.HelpOutline, contentDescription = "帮助")
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
}
|
Scaffold(
|
||||||
|
|
||||||
IconButton(onClick = {
|
|
||||||
viewModel.save(onSaveSuccess)
|
|
||||||
}) {
|
|
||||||
Icon(Icons.Default.Save, contentDescription = "保存")
|
|
||||||
}
|
|
||||||
|
|
||||||
IconButton(onClick = { showMenu = true }) {
|
|
||||||
Icon(Icons.Default.MoreVert, contentDescription = "更多操作")
|
|
||||||
}
|
|
||||||
|
|
||||||
DropdownMenu(
|
|
||||||
expanded = showMenu,
|
|
||||||
onDismissRequest = { showMenu = false }
|
|
||||||
) {
|
|
||||||
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text("复制规则") },
|
|
||||||
onClick = {
|
|
||||||
showMenu = false
|
|
||||||
viewModel.copyRule()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text("粘贴规则") },
|
|
||||||
onClick = {
|
|
||||||
showMenu = false
|
|
||||||
viewModel.pasteRule(onSuccess = {})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { paddingValues ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(paddingValues)
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
.fillMaxSize()
|
.sharedBounds(
|
||||||
.verticalScroll(rememberScrollState())
|
sharedContentState = rememberSharedContentState(key = sharedKey),
|
||||||
.imePadding()
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
//clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(16.dp))
|
||||||
) {
|
),
|
||||||
|
topBar = {
|
||||||
OutlinedTextField(
|
GlassMediumFlexibleTopAppBar(
|
||||||
value = state.name,
|
title = { Text(if (state.id > 0) "编辑替换规则" else "新增替换规则") },
|
||||||
onValueChange = viewModel::onNameChange,
|
navigationIcon = {
|
||||||
label = { Text("规则名称") },
|
IconButton(onClick = onBack) {
|
||||||
modifier = Modifier
|
Icon(
|
||||||
.fillMaxWidth()
|
Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
.onFocusChanged {
|
"Back"
|
||||||
if (it.isFocused) viewModel.activeField =
|
)
|
||||||
ReplaceEditViewModel.ActiveField.Name
|
}
|
||||||
},
|
},
|
||||||
singleLine = true
|
actions = {
|
||||||
)
|
|
||||||
|
|
||||||
GroupSelector(
|
IconButton(onClick = { onShowHelp("regexHelp") }) {
|
||||||
currentGroup = state.group,
|
Icon(
|
||||||
allGroups = state.allGroups,
|
Icons.AutoMirrored.Filled.HelpOutline,
|
||||||
onGroupChange = viewModel::onGroupChange,
|
contentDescription = "帮助"
|
||||||
onManageClick = { viewModel.toggleGroupDialog(true) }
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
OutlinedTextField(
|
IconButton(onClick = {
|
||||||
value = state.pattern,
|
viewModel.save(onSaveSuccess)
|
||||||
onValueChange = viewModel::onPatternChange,
|
}) {
|
||||||
label = { Text("匹配规则") },
|
Icon(Icons.Default.Save, contentDescription = "保存")
|
||||||
placeholder = { Text("输入正则表达式或关键字") },
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = { showMenu = true }) {
|
||||||
|
Icon(Icons.Default.MoreVert, contentDescription = "更多操作")
|
||||||
|
}
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showMenu,
|
||||||
|
onDismissRequest = { showMenu = false }
|
||||||
|
) {
|
||||||
|
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text("复制规则") },
|
||||||
|
onClick = {
|
||||||
|
showMenu = false
|
||||||
|
viewModel.copyRule()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text("粘贴规则") },
|
||||||
|
onClick = {
|
||||||
|
showMenu = false
|
||||||
|
viewModel.pasteRule(onSuccess = {})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.padding(paddingValues)
|
||||||
.onFocusChanged {
|
.fillMaxSize()
|
||||||
if (it.isFocused) viewModel.activeField =
|
.verticalScroll(rememberScrollState())
|
||||||
ReplaceEditViewModel.ActiveField.Pattern
|
.imePadding()
|
||||||
}
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = state.replacement,
|
|
||||||
onValueChange = viewModel::onReplacementChange,
|
|
||||||
label = { Text("替换为") },
|
|
||||||
placeholder = { Text("输入替换内容或捕获组") },
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.onFocusChanged {
|
|
||||||
if (it.isFocused) viewModel.activeField =
|
|
||||||
ReplaceEditViewModel.ActiveField.Replacement
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.Start
|
|
||||||
) {
|
) {
|
||||||
FilterChip(
|
|
||||||
selected = state.scopeTitle,
|
OutlinedTextField(
|
||||||
onClick = { viewModel.onScopeTitleChange(!state.scopeTitle) },
|
value = state.name,
|
||||||
label = { Text("标题") },
|
onValueChange = viewModel::onNameChange,
|
||||||
leadingIcon = if (state.scopeTitle) {
|
label = { Text("规则名称") },
|
||||||
{ Icon(Icons.Default.Check, contentDescription = "已选", Modifier.size(FilterChipDefaults.IconSize)) }
|
modifier = Modifier
|
||||||
} else null
|
.fillMaxWidth()
|
||||||
|
.onFocusChanged {
|
||||||
|
if (it.isFocused) viewModel.activeField =
|
||||||
|
ReplaceEditViewModel.ActiveField.Name
|
||||||
|
},
|
||||||
|
singleLine = true
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(Modifier.width(8.dp))
|
GroupSelector(
|
||||||
|
currentGroup = state.group,
|
||||||
FilterChip(
|
allGroups = state.allGroups,
|
||||||
selected = state.scopeContent,
|
onGroupChange = viewModel::onGroupChange,
|
||||||
onClick = { viewModel.onScopeContentChange(!state.scopeContent) },
|
onManageClick = { viewModel.toggleGroupDialog(true) }
|
||||||
label = { Text("内容") },
|
|
||||||
leadingIcon = if (state.scopeContent) {
|
|
||||||
{ Icon(Icons.Default.Check, contentDescription = "已选", Modifier.size(FilterChipDefaults.IconSize)) }
|
|
||||||
} else null
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(Modifier.weight(1f))
|
OutlinedTextField(
|
||||||
|
value = state.pattern,
|
||||||
FilterChip(
|
onValueChange = viewModel::onPatternChange,
|
||||||
selected = state.isRegex,
|
label = { Text("匹配规则") },
|
||||||
onClick = { viewModel.onRegexChange(!state.isRegex) },
|
placeholder = { Text("输入正则表达式或关键字") },
|
||||||
label = { Text("使用正则") },
|
modifier = Modifier
|
||||||
leadingIcon = if (state.isRegex) {
|
.fillMaxWidth()
|
||||||
{ Icon(Icons.Default.Check, contentDescription = "正则已启用", Modifier.size(FilterChipDefaults.IconSize)) }
|
.onFocusChanged {
|
||||||
} else null
|
if (it.isFocused) viewModel.activeField =
|
||||||
|
ReplaceEditViewModel.ActiveField.Pattern
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = state.replacement,
|
||||||
|
onValueChange = viewModel::onReplacementChange,
|
||||||
|
label = { Text("替换为") },
|
||||||
|
placeholder = { Text("输入替换内容或捕获组") },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.onFocusChanged {
|
||||||
|
if (it.isFocused) viewModel.activeField =
|
||||||
|
ReplaceEditViewModel.ActiveField.Replacement
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Start
|
||||||
|
) {
|
||||||
|
FilterChip(
|
||||||
|
selected = state.scopeTitle,
|
||||||
|
onClick = { viewModel.onScopeTitleChange(!state.scopeTitle) },
|
||||||
|
label = { Text("标题") },
|
||||||
|
leadingIcon = if (state.scopeTitle) {
|
||||||
|
{
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Check,
|
||||||
|
contentDescription = "已选",
|
||||||
|
Modifier.size(FilterChipDefaults.IconSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
|
||||||
|
FilterChip(
|
||||||
|
selected = state.scopeContent,
|
||||||
|
onClick = { viewModel.onScopeContentChange(!state.scopeContent) },
|
||||||
|
label = { Text("内容") },
|
||||||
|
leadingIcon = if (state.scopeContent) {
|
||||||
|
{
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Check,
|
||||||
|
contentDescription = "已选",
|
||||||
|
Modifier.size(FilterChipDefaults.IconSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
|
||||||
|
FilterChip(
|
||||||
|
selected = state.isRegex,
|
||||||
|
onClick = { viewModel.onRegexChange(!state.isRegex) },
|
||||||
|
label = { Text("使用正则") },
|
||||||
|
leadingIcon = if (state.isRegex) {
|
||||||
|
{
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Check,
|
||||||
|
contentDescription = "正则已启用",
|
||||||
|
Modifier.size(FilterChipDefaults.IconSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = state.scope,
|
||||||
|
onValueChange = viewModel::onScopeChange,
|
||||||
|
label = { Text("特定范围") },
|
||||||
|
placeholder = { Text("指定规则适用的范围") },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.onFocusChanged {
|
||||||
|
if (it.isFocused) viewModel.activeField =
|
||||||
|
ReplaceEditViewModel.ActiveField.Scope
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = state.excludeScope,
|
||||||
|
onValueChange = viewModel::onExcludeScopeChange,
|
||||||
|
label = { Text("排除范围") },
|
||||||
|
placeholder = { Text("指定规则不适用的范围") },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.onFocusChanged {
|
||||||
|
if (it.isFocused) viewModel.activeField =
|
||||||
|
ReplaceEditViewModel.ActiveField.Exclude
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = state.timeout,
|
||||||
|
onValueChange = viewModel::onTimeoutChange,
|
||||||
|
label = { Text("超时 (ms)") },
|
||||||
|
placeholder = { Text("3000") },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OutlinedTextField(
|
AnimatedVisibility(
|
||||||
value = state.scope,
|
visible = isKeyboardVisible,
|
||||||
onValueChange = viewModel::onScopeChange,
|
enter = slideInVertically(
|
||||||
label = { Text("特定范围") },
|
initialOffsetY = { fullHeight -> fullHeight },
|
||||||
placeholder = { Text("指定规则适用的范围") },
|
),
|
||||||
modifier = Modifier
|
exit = slideOutVertically(
|
||||||
.fillMaxWidth()
|
targetOffsetY = { fullHeight -> fullHeight },
|
||||||
.onFocusChanged {
|
),
|
||||||
if (it.isFocused) viewModel.activeField =
|
modifier = Modifier
|
||||||
ReplaceEditViewModel.ActiveField.Scope
|
.align(Alignment.BottomCenter)
|
||||||
}
|
.imePadding()
|
||||||
|
) {
|
||||||
|
QuickInputBar(
|
||||||
|
onInsert = { text -> viewModel.insertTextAtCursor(text) }
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = state.excludeScope,
|
if (state.showGroupDialog) {
|
||||||
onValueChange = viewModel::onExcludeScopeChange,
|
ManageGroupDialog(
|
||||||
label = { Text("排除范围") },
|
groups = state.allGroups.filter { it != "默认" },
|
||||||
placeholder = { Text("指定规则不适用的范围") },
|
onDismiss = { viewModel.toggleGroupDialog(false) },
|
||||||
modifier = Modifier
|
onDelete = { viewModel.deleteGroups(it) }
|
||||||
.fillMaxWidth()
|
|
||||||
.onFocusChanged {
|
|
||||||
if (it.isFocused) viewModel.activeField =
|
|
||||||
ReplaceEditViewModel.ActiveField.Exclude
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = state.timeout,
|
|
||||||
onValueChange = viewModel::onTimeoutChange,
|
|
||||||
label = { Text("超时 (ms)") },
|
|
||||||
placeholder = { Text("3000") },
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = isKeyboardVisible,
|
|
||||||
enter = slideInVertically(
|
|
||||||
initialOffsetY = { fullHeight -> fullHeight },
|
|
||||||
),
|
|
||||||
exit = slideOutVertically(
|
|
||||||
targetOffsetY = { fullHeight -> fullHeight },
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
.imePadding()
|
|
||||||
) {
|
|
||||||
QuickInputBar(
|
|
||||||
onInsert = { text -> viewModel.insertTextAtCursor(text) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (state.showGroupDialog) {
|
|
||||||
ManageGroupDialog(
|
|
||||||
groups = state.allGroups.filter { it != "默认" },
|
|
||||||
onDismiss = { viewModel.toggleGroupDialog(false) },
|
|
||||||
onDelete = { viewModel.deleteGroups(it) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|||||||
@@ -6,21 +6,26 @@ import androidx.compose.ui.text.input.TextFieldValue
|
|||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.navigation.toRoute
|
||||||
import io.legado.app.data.dao.ReplaceRuleDao
|
import io.legado.app.data.dao.ReplaceRuleDao
|
||||||
import io.legado.app.data.entities.ReplaceRule
|
import io.legado.app.data.entities.ReplaceRule
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
|
import io.legado.app.ui.replace.ReplaceEditRoute
|
||||||
import io.legado.app.utils.GSON
|
import io.legado.app.utils.GSON
|
||||||
import io.legado.app.utils.fromJsonObject
|
import io.legado.app.utils.fromJsonObject
|
||||||
import io.legado.app.utils.getClipText
|
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.*
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
data class ReplaceEditUiState(
|
data class ReplaceEditUiState(
|
||||||
val id: Long = System.currentTimeMillis(),
|
val id: Long = 0,
|
||||||
val name: TextFieldValue = TextFieldValue(""),
|
val name: TextFieldValue = TextFieldValue(""),
|
||||||
val group: String = "默认",
|
val group: String = "默认",
|
||||||
val pattern: TextFieldValue = TextFieldValue(""),
|
val pattern: TextFieldValue = TextFieldValue(""),
|
||||||
@@ -41,6 +46,8 @@ class ReplaceEditViewModel(
|
|||||||
private val savedStateHandle: SavedStateHandle
|
private val savedStateHandle: SavedStateHandle
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val route = savedStateHandle.toRoute<ReplaceEditRoute>()
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(ReplaceEditUiState())
|
private val _uiState = MutableStateFlow(ReplaceEditUiState())
|
||||||
val uiState = _uiState.asStateFlow()
|
val uiState = _uiState.asStateFlow()
|
||||||
|
|
||||||
@@ -54,19 +61,22 @@ class ReplaceEditViewModel(
|
|||||||
|
|
||||||
private fun initData() {
|
private fun initData() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val id = savedStateHandle.get<Long>("id") ?: -1L
|
|
||||||
|
val id = route.id
|
||||||
|
|
||||||
if (id > 0) {
|
if (id > 0) {
|
||||||
val rule = replaceRuleDao.findById(id)
|
val rule = replaceRuleDao.findById(id)
|
||||||
rule?.let { updateStateFromRule(it) }
|
rule?.let { updateStateFromRule(it) }
|
||||||
} else {
|
} else {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
id = System.currentTimeMillis(),
|
||||||
name = TextFieldValue(""),
|
name = TextFieldValue(""),
|
||||||
pattern = TextFieldValue(savedStateHandle["pattern"] ?: ""),
|
pattern = TextFieldValue(route.pattern ?: ""),
|
||||||
isRegex = savedStateHandle["isRegex"] ?: false,
|
isRegex = route.isRegex,
|
||||||
scope = TextFieldValue(savedStateHandle["scope"] ?: ""),
|
scope = TextFieldValue(route.scope ?: ""),
|
||||||
scopeTitle = savedStateHandle["isScopeTitle"] ?: false,
|
scopeTitle = route.isScopeTitle,
|
||||||
scopeContent = savedStateHandle["isScopeContent"] ?: false,
|
scopeContent = route.isScopeContent,
|
||||||
excludeScope = TextFieldValue(""),
|
excludeScope = TextFieldValue(""),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ plugins {
|
|||||||
alias(libs.plugins.android.library) apply false
|
alias(libs.plugins.android.library) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
alias(libs.plugins.kotlin.parcelize) apply false
|
alias(libs.plugins.kotlin.parcelize) apply false
|
||||||
|
alias(libs.plugins.kotlin.serialization) apply false
|
||||||
alias(libs.plugins.ksp) apply false
|
alias(libs.plugins.ksp) apply false
|
||||||
alias(libs.plugins.google.services) apply false
|
alias(libs.plugins.google.services) apply false
|
||||||
alias(libs.plugins.room) apply false
|
alias(libs.plugins.room) apply false
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
accompanistWebview = "0.36.0"
|
accompanistWebview = "0.36.0"
|
||||||
animation = "1.10.0"
|
adaptive = "1.3.0-alpha06"
|
||||||
|
adaptiveLayout = "1.3.0-alpha06"
|
||||||
|
adaptiveNavigation = "1.3.0-alpha06"
|
||||||
|
animation = "1.10.1"
|
||||||
biometric = "1.2.0"
|
biometric = "1.2.0"
|
||||||
coilCompose = "2.7.0"
|
coilCompose = "2.7.0"
|
||||||
composeBom = "2025.12.01"
|
composeBom = "2026.01.00"
|
||||||
constraintlayoutCompose = "1.1.1"
|
constraintlayoutCompose = "1.1.1"
|
||||||
coreSplashscreen = "1.2.0"
|
coreSplashscreen = "1.2.0"
|
||||||
datastorePreferences = "1.2.0"
|
datastorePreferences = "1.2.0"
|
||||||
foundation = "1.10.0"
|
foundation = "1.10.1"
|
||||||
kotlin = "2.3.0"
|
kotlin = "2.3.0"
|
||||||
|
kotlinxSerialization = "1.10.0"
|
||||||
kotlinxCoroutinesAndroid = "1.10.2"
|
kotlinxCoroutinesAndroid = "1.10.2"
|
||||||
|
kotlinxSerializationJson = "1.10.0"
|
||||||
ksp = "2.3.4"
|
ksp = "2.3.4"
|
||||||
agp = "8.13.2"
|
agp = "8.13.2"
|
||||||
appcompat = "1.7.1"
|
appcompat = "1.7.1"
|
||||||
@@ -37,10 +42,11 @@ jsoupxpath = "2.5.3"
|
|||||||
coroutines = "1.10.2"
|
coroutines = "1.10.2"
|
||||||
lifecycleViewmodelCompose = "2.10.0"
|
lifecycleViewmodelCompose = "2.10.0"
|
||||||
liveeventbus = "1.8.14"
|
liveeventbus = "1.8.14"
|
||||||
|
navigation3 = "1.0.0"
|
||||||
markdownCompose = "0.5.0"
|
markdownCompose = "0.5.0"
|
||||||
markwon = "4.6.2"
|
markwon = "4.6.2"
|
||||||
material = "1.14.0-alpha08"
|
material = "1.14.0-alpha08"
|
||||||
material3 = "1.5.0-alpha11"
|
material3 = "1.5.0-alpha12"
|
||||||
material3IconsExtended = "1.7.8"
|
material3IconsExtended = "1.7.8"
|
||||||
materialKolor = "4.1.0"
|
materialKolor = "4.1.0"
|
||||||
media = "1.7.1"
|
media = "1.7.1"
|
||||||
@@ -89,10 +95,15 @@ androidx-annotation = { group = "androidx.annotation", name = "annotation", vers
|
|||||||
|
|
||||||
androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }
|
androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }
|
||||||
androidx-collection = { module = "androidx.collection:collection", version.ref = "collection" }
|
androidx-collection = { module = "androidx.collection:collection", version.ref = "collection" }
|
||||||
|
androidx-compose-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "adaptive" }
|
||||||
|
androidx-compose-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "adaptiveLayout" }
|
||||||
|
androidx-compose-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "adaptiveNavigation" }
|
||||||
androidx-compose-animation = { module = "androidx.compose.animation:animation", version.ref = "animation" }
|
androidx-compose-animation = { module = "androidx.compose.animation:animation", version.ref = "animation" }
|
||||||
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
|
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
|
||||||
androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "foundation" }
|
androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "foundation" }
|
||||||
androidx-compose-material = { module = "androidx.compose.material:material" }
|
androidx-compose-material = { module = "androidx.compose.material:material" }
|
||||||
|
androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "navigation3" }
|
||||||
|
androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "navigation3" }
|
||||||
compose-materialIcons = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "material3IconsExtended" }
|
compose-materialIcons = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "material3IconsExtended" }
|
||||||
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
|
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
|
||||||
androidx-compose-ui = { module = "androidx.compose.ui:ui" }
|
androidx-compose-ui = { module = "androidx.compose.ui:ui" }
|
||||||
@@ -138,6 +149,7 @@ androidx-junit = { module = "androidx.test.ext:junit", version = "1.3.0" }
|
|||||||
hutool-crypto = { module = "cn.hutool:hutool-crypto", version.ref = "hutool" }
|
hutool-crypto = { module = "cn.hutool:hutool-crypto", version.ref = "hutool" }
|
||||||
|
|
||||||
kotlinx-coroutines-android-v181 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" }
|
kotlinx-coroutines-android-v181 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" }
|
||||||
|
kotlinx-serialization-json-v163 = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
||||||
libarchive = { module = "me.zhanghai.android.libarchive:library", version.ref = "libarchive" }
|
libarchive = { module = "me.zhanghai.android.libarchive:library", version.ref = "libarchive" }
|
||||||
lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "lifecycle" }
|
lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "lifecycle" }
|
||||||
lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "lifecycle" }
|
lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "lifecycle" }
|
||||||
@@ -206,7 +218,7 @@ jsoupxpath = { module = "cn.wanghaomiao:JsoupXpath", version.ref = "jsoupxpath"
|
|||||||
junit = { module = "junit:junit", version = "4.13.2" }
|
junit = { module = "junit:junit", version = "4.13.2" }
|
||||||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
|
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
|
||||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||||
|
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
||||||
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
|
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
|
||||||
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user