[修复] 替换净化编辑或修改时无法正确更新正文的问题 #583
[新增] 替换净化编辑与管理界面使用Navigation处理导航逻辑与优化动画
This commit is contained in:
@@ -6,6 +6,7 @@ plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.room)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.google.services)
|
||||
@@ -181,6 +182,7 @@ dependencies {
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.bundles.androidTest)
|
||||
implementation(libs.kotlin.stdlib)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.bundles.coroutines)
|
||||
implementation(libs.core.ktx)
|
||||
implementation(libs.appcompat.appcompat)
|
||||
@@ -256,6 +258,11 @@ dependencies {
|
||||
implementation(libs.androidx.compose.foundation)
|
||||
implementation(libs.androidx.constraintlayout.compose)
|
||||
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.compose.material)
|
||||
implementation(libs.compose.materialIcons)
|
||||
|
||||
@@ -211,13 +211,6 @@
|
||||
android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
|
||||
android:launchMode="singleTask"
|
||||
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
|
||||
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.searchContent.SearchContentViewModel
|
||||
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.edit.ReplaceEditViewModel
|
||||
import org.koin.android.ext.koin.androidApplication
|
||||
@@ -34,7 +35,13 @@ val appModule = module {
|
||||
single { SearchContentRepository() }
|
||||
|
||||
viewModel { ReadRecordViewModel(get(), get()) }
|
||||
viewModel { ReplaceEditViewModel(get(), get(), get()) }
|
||||
viewModel { (route: ReplaceEditRoute) ->
|
||||
ReplaceEditViewModel(
|
||||
app = get(),
|
||||
replaceRuleDao = get(),
|
||||
savedStateHandle = get()
|
||||
)
|
||||
}
|
||||
viewModel { ReplaceRuleViewModel(androidApplication()) }
|
||||
viewModel { ExploreShowViewModel(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.theme.primaryColor
|
||||
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.viewbindingdelegate.viewBinding
|
||||
|
||||
@@ -95,7 +96,8 @@ class EffectiveReplacesDialog : BaseDialogFragment(R.layout.dialog_recycler_view
|
||||
showChineseConvertAlert()
|
||||
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.dict.DictDialog
|
||||
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.edit.ReplaceEditActivity
|
||||
import io.legado.app.ui.widget.PopupAction
|
||||
import io.legado.app.ui.widget.dialog.PhotoDialog
|
||||
import io.legado.app.utils.Debounce
|
||||
@@ -981,20 +981,23 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
|
||||
R.id.menu_replace -> {
|
||||
val scopes = arrayListOf<String>()
|
||||
ReadBook.book?.name?.let {
|
||||
scopes.add(it)
|
||||
}
|
||||
ReadBook.bookSource?.bookSourceUrl?.let {
|
||||
scopes.add(it)
|
||||
}
|
||||
ReadBook.book?.name?.let { scopes.add(it) }
|
||||
ReadBook.bookSource?.bookSourceUrl?.let { scopes.add(it) }
|
||||
|
||||
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(
|
||||
ReplaceEditActivity.startIntent(
|
||||
this,
|
||||
pattern = text,
|
||||
scope = scopes.joinToString(";"),
|
||||
isScopeTitle = false,
|
||||
isScopeContent = true
|
||||
ReplaceRuleActivity.startIntent(
|
||||
context = this,
|
||||
editRoute = editRoute
|
||||
)
|
||||
)
|
||||
return true
|
||||
|
||||
@@ -14,13 +14,11 @@ import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
@@ -29,7 +27,6 @@ import io.legado.app.R
|
||||
import io.legado.app.base.VMBaseActivity
|
||||
import io.legado.app.constant.EventBus
|
||||
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.DialogDownloadChoiceBinding
|
||||
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.book.toc.rule.TxtTocRuleDialog
|
||||
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.edit.ReplaceEditActivity
|
||||
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.hideSoftInput
|
||||
import io.legado.app.utils.observeEvent
|
||||
import io.legado.app.utils.shouldHideSoftInput
|
||||
import io.legado.app.utils.showDialogFragment
|
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||
import io.legado.app.utils.visible
|
||||
@@ -262,14 +255,17 @@ class TocActivity : VMBaseActivity<ActivityChapterListBinding, TocViewModel>(),
|
||||
val scopes = arrayListOf<String>()
|
||||
viewModel.bookData.value?.name?.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(
|
||||
ReplaceEditActivity.startIntent(
|
||||
this,
|
||||
pattern = "text",
|
||||
scope = scopes.joinToString(";"),
|
||||
isScopeTitle = true,
|
||||
isScopeContent = false
|
||||
)
|
||||
ReplaceRuleActivity.startIntent(this, editRoute)
|
||||
)
|
||||
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
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.animation.SharedTransitionLayout
|
||||
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.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() {
|
||||
|
||||
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
|
||||
override fun Content() {
|
||||
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.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
@@ -86,7 +88,6 @@ import com.google.gson.Gson
|
||||
import io.legado.app.R
|
||||
import io.legado.app.data.entities.ReplaceRule
|
||||
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.AnimatedText
|
||||
import io.legado.app.ui.widget.components.DraggableSelectionHandler
|
||||
@@ -111,8 +112,11 @@ import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||
)
|
||||
@Composable
|
||||
fun ReplaceRuleScreen(
|
||||
sharedTransitionScope: SharedTransitionScope,
|
||||
animatedVisibilityScope: AnimatedVisibilityScope,
|
||||
viewModel: ReplaceRuleViewModel = koinViewModel(),
|
||||
onBackClick: () -> Unit
|
||||
onBackClick: () -> Unit,
|
||||
onNavigateToEdit: (ReplaceEditRoute) -> Unit,
|
||||
) {
|
||||
|
||||
val uploadRepository: UploadRepository = koinInject()
|
||||
@@ -519,16 +523,24 @@ fun ReplaceRuleScreen(
|
||||
tooltip = { PlainTooltip { Text("Localized description") } },
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
FloatingActionButton(
|
||||
modifier = Modifier.animateFloatingActionButton(
|
||||
visible = !inSelectionMode,
|
||||
alignment = Alignment.BottomEnd,
|
||||
),
|
||||
onClick = {
|
||||
context.startActivity(ReplaceEditActivity.startIntent(context))
|
||||
with(sharedTransitionScope) {
|
||||
FloatingActionButton(
|
||||
modifier = Modifier
|
||||
.animateFloatingActionButton(
|
||||
visible = !inSelectionMode,
|
||||
alignment = Alignment.BottomEnd,
|
||||
)
|
||||
.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)
|
||||
) {
|
||||
items(rules, key = { it.id }) { ui ->
|
||||
ReorderableSelectionItem(
|
||||
state = reorderableState,
|
||||
key = ui.id,
|
||||
title = ui.name,
|
||||
isEnabled = ui.isEnabled,
|
||||
isSelected = selectedRuleIds.contains(ui.id),
|
||||
inSelectionMode = inSelectionMode,
|
||||
canReorder = canReorder,
|
||||
onToggleSelection = {
|
||||
viewModel.toggleSelection(ui.id)
|
||||
},
|
||||
onEnabledChange = { enabled ->
|
||||
viewModel.update(ui.rule.copy(isEnabled = enabled))
|
||||
},
|
||||
onClickEdit = {
|
||||
context.startActivity(
|
||||
ReplaceEditActivity.startIntent(
|
||||
context,
|
||||
ui.id
|
||||
with(sharedTransitionScope) {
|
||||
ReorderableSelectionItem(
|
||||
state = reorderableState,
|
||||
key = ui.id,
|
||||
title = ui.name,
|
||||
isEnabled = ui.isEnabled,
|
||||
isSelected = selectedRuleIds.contains(ui.id),
|
||||
inSelectionMode = inSelectionMode,
|
||||
canReorder = canReorder,
|
||||
onToggleSelection = {
|
||||
viewModel.toggleSelection(ui.id)
|
||||
},
|
||||
onEnabledChange = { enabled ->
|
||||
viewModel.update(ui.rule.copy(isEnabled = enabled))
|
||||
},
|
||||
onClickEdit = {
|
||||
onNavigateToEdit(
|
||||
ReplaceEditRoute(
|
||||
id = ui.id,
|
||||
pattern = ui.rule.pattern
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
dropdownContent = { dismiss ->
|
||||
DropdownMenuItem(
|
||||
text = { Text("移至顶部") },
|
||||
onClick = { viewModel.toTop(ui.rule); dismiss() }
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("移至底部") },
|
||||
onClick = { viewModel.toBottom(ui.rule); dismiss() }
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("删除") },
|
||||
onClick = { showDeleteRuleDialog = ui.rule; dismiss() }
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp)
|
||||
.sharedBounds(
|
||||
sharedContentState = rememberSharedContentState(key = "rule_${ui.id}"),
|
||||
animatedVisibilityScope = animatedVisibilityScope,
|
||||
resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds,
|
||||
//clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(12.dp))
|
||||
),
|
||||
dropdownContent = { dismiss ->
|
||||
DropdownMenuItem(
|
||||
text = { Text("移至顶部") },
|
||||
onClick = { viewModel.toTop(ui.rule); dismiss() }
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("移至底部") },
|
||||
onClick = { viewModel.toBottom(ui.rule); dismiss() }
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("删除") },
|
||||
onClick = { showDeleteRuleDialog = ui.rule; dismiss() }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.background
|
||||
@@ -82,225 +84,267 @@ fun keyboardAsState(): State<Boolean> {
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun ReplaceEditScreen(
|
||||
sharedTransitionScope: SharedTransitionScope,
|
||||
animatedVisibilityScope: AnimatedVisibilityScope,
|
||||
onBack: () -> Unit,
|
||||
onSaveSuccess: () -> Unit,
|
||||
onShowHelp: (String) -> Unit,
|
||||
viewModel: ReplaceEditViewModel = koinViewModel()
|
||||
) {
|
||||
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()
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
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") }) {
|
||||
Icon(Icons.AutoMirrored.Filled.HelpOutline, contentDescription = "帮助")
|
||||
}
|
||||
|
||||
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(
|
||||
with(sharedTransitionScope) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.imePadding()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.name,
|
||||
onValueChange = viewModel::onNameChange,
|
||||
label = { Text("规则名称") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
if (it.isFocused) viewModel.activeField =
|
||||
ReplaceEditViewModel.ActiveField.Name
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.sharedBounds(
|
||||
sharedContentState = rememberSharedContentState(key = sharedKey),
|
||||
animatedVisibilityScope = animatedVisibilityScope,
|
||||
resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds,
|
||||
//clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(16.dp))
|
||||
),
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = { Text(if (state.id > 0) "编辑替换规则" else "新增替换规则") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.ArrowBack,
|
||||
"Back"
|
||||
)
|
||||
}
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
actions = {
|
||||
|
||||
GroupSelector(
|
||||
currentGroup = state.group,
|
||||
allGroups = state.allGroups,
|
||||
onGroupChange = viewModel::onGroupChange,
|
||||
onManageClick = { viewModel.toggleGroupDialog(true) }
|
||||
)
|
||||
IconButton(onClick = { onShowHelp("regexHelp") }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.HelpOutline,
|
||||
contentDescription = "帮助"
|
||||
)
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.pattern,
|
||||
onValueChange = viewModel::onPatternChange,
|
||||
label = { Text("匹配规则") },
|
||||
placeholder = { Text("输入正则表达式或关键字") },
|
||||
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
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
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
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.imePadding()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
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
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.name,
|
||||
onValueChange = viewModel::onNameChange,
|
||||
label = { Text("规则名称") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
if (it.isFocused) viewModel.activeField =
|
||||
ReplaceEditViewModel.ActiveField.Name
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
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
|
||||
GroupSelector(
|
||||
currentGroup = state.group,
|
||||
allGroups = state.allGroups,
|
||||
onGroupChange = viewModel::onGroupChange,
|
||||
onManageClick = { viewModel.toggleGroupDialog(true) }
|
||||
)
|
||||
|
||||
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.pattern,
|
||||
onValueChange = viewModel::onPatternChange,
|
||||
label = { Text("匹配规则") },
|
||||
placeholder = { Text("输入正则表达式或关键字") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
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(
|
||||
value = state.scope,
|
||||
onValueChange = viewModel::onScopeChange,
|
||||
label = { Text("特定范围") },
|
||||
placeholder = { Text("指定规则适用的范围") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
if (it.isFocused) viewModel.activeField =
|
||||
ReplaceEditViewModel.ActiveField.Scope
|
||||
}
|
||||
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) }
|
||||
)
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.excludeScope,
|
||||
onValueChange = viewModel::onExcludeScopeChange,
|
||||
label = { Text("排除范围") },
|
||||
placeholder = { Text("指定规则不适用的范围") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
if (it.isFocused) viewModel.activeField =
|
||||
ReplaceEditViewModel.ActiveField.Exclude
|
||||
}
|
||||
|
||||
if (state.showGroupDialog) {
|
||||
ManageGroupDialog(
|
||||
groups = state.allGroups.filter { it != "默认" },
|
||||
onDismiss = { viewModel.toggleGroupDialog(false) },
|
||||
onDelete = { viewModel.deleteGroups(it) }
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
@@ -6,21 +6,26 @@ import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.toRoute
|
||||
import io.legado.app.data.dao.ReplaceRuleDao
|
||||
import io.legado.app.data.entities.ReplaceRule
|
||||
import io.legado.app.exception.NoStackTraceException
|
||||
import io.legado.app.ui.replace.ReplaceEditRoute
|
||||
import io.legado.app.utils.GSON
|
||||
import io.legado.app.utils.fromJsonObject
|
||||
import io.legado.app.utils.getClipText
|
||||
import io.legado.app.utils.sendToClip
|
||||
import io.legado.app.utils.toastOnUi
|
||||
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
|
||||
|
||||
|
||||
data class ReplaceEditUiState(
|
||||
val id: Long = System.currentTimeMillis(),
|
||||
val id: Long = 0,
|
||||
val name: TextFieldValue = TextFieldValue(""),
|
||||
val group: String = "默认",
|
||||
val pattern: TextFieldValue = TextFieldValue(""),
|
||||
@@ -41,6 +46,8 @@ class ReplaceEditViewModel(
|
||||
private val savedStateHandle: SavedStateHandle
|
||||
) : ViewModel() {
|
||||
|
||||
private val route = savedStateHandle.toRoute<ReplaceEditRoute>()
|
||||
|
||||
private val _uiState = MutableStateFlow(ReplaceEditUiState())
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
@@ -54,19 +61,22 @@ class ReplaceEditViewModel(
|
||||
|
||||
private fun initData() {
|
||||
viewModelScope.launch {
|
||||
val id = savedStateHandle.get<Long>("id") ?: -1L
|
||||
|
||||
val id = route.id
|
||||
|
||||
if (id > 0) {
|
||||
val rule = replaceRuleDao.findById(id)
|
||||
rule?.let { updateStateFromRule(it) }
|
||||
} else {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
id = System.currentTimeMillis(),
|
||||
name = TextFieldValue(""),
|
||||
pattern = TextFieldValue(savedStateHandle["pattern"] ?: ""),
|
||||
isRegex = savedStateHandle["isRegex"] ?: false,
|
||||
scope = TextFieldValue(savedStateHandle["scope"] ?: ""),
|
||||
scopeTitle = savedStateHandle["isScopeTitle"] ?: false,
|
||||
scopeContent = savedStateHandle["isScopeContent"] ?: false,
|
||||
pattern = TextFieldValue(route.pattern ?: ""),
|
||||
isRegex = route.isRegex,
|
||||
scope = TextFieldValue(route.scope ?: ""),
|
||||
scopeTitle = route.isScopeTitle,
|
||||
scopeContent = route.isScopeContent,
|
||||
excludeScope = TextFieldValue(""),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ plugins {
|
||||
alias(libs.plugins.android.library) apply false
|
||||
alias(libs.plugins.kotlin.android) 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.google.services) apply false
|
||||
alias(libs.plugins.room) apply false
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
[versions]
|
||||
|
||||
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"
|
||||
coilCompose = "2.7.0"
|
||||
composeBom = "2025.12.01"
|
||||
composeBom = "2026.01.00"
|
||||
constraintlayoutCompose = "1.1.1"
|
||||
coreSplashscreen = "1.2.0"
|
||||
datastorePreferences = "1.2.0"
|
||||
foundation = "1.10.0"
|
||||
foundation = "1.10.1"
|
||||
kotlin = "2.3.0"
|
||||
kotlinxSerialization = "1.10.0"
|
||||
kotlinxCoroutinesAndroid = "1.10.2"
|
||||
kotlinxSerializationJson = "1.10.0"
|
||||
ksp = "2.3.4"
|
||||
agp = "8.13.2"
|
||||
appcompat = "1.7.1"
|
||||
@@ -37,10 +42,11 @@ jsoupxpath = "2.5.3"
|
||||
coroutines = "1.10.2"
|
||||
lifecycleViewmodelCompose = "2.10.0"
|
||||
liveeventbus = "1.8.14"
|
||||
navigation3 = "1.0.0"
|
||||
markdownCompose = "0.5.0"
|
||||
markwon = "4.6.2"
|
||||
material = "1.14.0-alpha08"
|
||||
material3 = "1.5.0-alpha11"
|
||||
material3 = "1.5.0-alpha12"
|
||||
material3IconsExtended = "1.7.8"
|
||||
materialKolor = "4.1.0"
|
||||
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-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-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
|
||||
androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "foundation" }
|
||||
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" }
|
||||
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
|
||||
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" }
|
||||
|
||||
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" }
|
||||
lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", 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" }
|
||||
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-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-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user