feat: modify subsItem updateUrl (#727)

This commit is contained in:
lisonge 2024-09-19 22:40:02 +08:00
parent 7ef4c51392
commit 1b5e60c67c
4 changed files with 180 additions and 83 deletions

View File

@ -0,0 +1,127 @@
package li.songe.gkd.ui.component
import android.webkit.URLUtil
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import li.songe.gkd.MainActivity
import li.songe.gkd.MainViewModel
import li.songe.gkd.util.isSafeUrl
import li.songe.gkd.util.launchAsFn
import li.songe.gkd.util.subsItemsFlow
import li.songe.gkd.util.throttle
import li.songe.gkd.util.toast
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class InputSubsLinkOption {
private val showFlow = MutableStateFlow(false)
private val valueFlow = MutableStateFlow("")
private val initValueFlow = MutableStateFlow("")
private var continuation: Continuation<String?>? = null
private fun resume(value: String?) {
showFlow.value = false
valueFlow.value = ""
initValueFlow.value = ""
continuation?.resume(value)
continuation = null
}
private suspend fun submit(mainVm: MainViewModel) {
val value = valueFlow.value
if (!URLUtil.isNetworkUrl(value)) {
toast("非法链接")
return
}
val initValue = initValueFlow.value
if (initValue.isNotEmpty() && initValue == value) {
toast("未修改")
resume(null)
return
}
if (subsItemsFlow.value.any { it.updateUrl == value }) {
toast("已有相同链接订阅")
return
}
if (!isSafeUrl(value)) {
mainVm.dialogFlow.waitResult(
title = "未知来源",
text = "你正在添加一个未验证的远程订阅\n\n这可能含有恶意的规则\n\n是否仍然确认添加?"
)
}
resume(value)
}
private fun cancel() = resume(null)
suspend fun getResult(initValue: String = ""): String? {
initValueFlow.value = initValue
valueFlow.value = initValue
showFlow.value = true
return suspendCoroutine {
continuation = it
}
}
@Composable
fun ContentDialog() {
val show by showFlow.collectAsState()
if (show) {
val context = LocalContext.current as MainActivity
val value by valueFlow.collectAsState()
val initValue by initValueFlow.collectAsState()
AlertDialog(
title = {
Text(text = if (initValue.isNotEmpty()) "修改订阅" else "添加订阅")
},
text = {
OutlinedTextField(
value = value,
onValueChange = {
valueFlow.value = it.trim()
},
maxLines = 8,
modifier = Modifier.fillMaxWidth(),
placeholder = {
Text(text = "请输入订阅链接")
},
isError = value.isNotEmpty() && !URLUtil.isNetworkUrl(value),
)
},
onDismissRequest = {
if (valueFlow.value.isEmpty()) {
cancel()
}
},
confirmButton = {
TextButton(
enabled = value.isNotEmpty(),
onClick = throttle(fn = context.mainVm.viewModelScope.launchAsFn {
submit(context.mainVm)
}),
) {
Text(text = "确定")
}
},
dismissButton = {
TextButton(onClick = ::cancel) {
Text(text = "取消")
}
},
)
}
}
}

View File

@ -237,7 +237,8 @@ private fun SubsMenuItem(
},
onClick = throttle {
onExpandedChange(false)
navController.toDestinationsNavigator().navigate(SubsPageDestination(subItem.id))
navController.toDestinationsNavigator()
.navigate(SubsPageDestination(subItem.id))
}
)
}
@ -248,7 +249,8 @@ private fun SubsMenuItem(
},
onClick = throttle {
onExpandedChange(false)
navController.toDestinationsNavigator().navigate(CategoryPageDestination(subItem.id))
navController.toDestinationsNavigator()
.navigate(CategoryPageDestination(subItem.id))
}
)
}
@ -259,11 +261,23 @@ private fun SubsMenuItem(
},
onClick = throttle {
onExpandedChange(false)
navController.toDestinationsNavigator().navigate(GlobalRulePageDestination(subItem.id))
navController.toDestinationsNavigator()
.navigate(GlobalRulePageDestination(subItem.id))
}
)
}
}
subscription?.supportUri?.let { supportUri ->
DropdownMenuItem(
text = {
Text(text = "问题反馈")
},
onClick = {
onExpandedChange(false)
context.openUri(supportUri)
}
)
}
DropdownMenuItem(
text = {
Text(text = "导出数据")
@ -286,15 +300,17 @@ private fun SubsMenuItem(
toast("复制成功")
}
)
}
subscription?.supportUri?.let { supportUri ->
DropdownMenuItem(
text = {
Text(text = "问题反馈")
Text(text = "修改链接")
},
onClick = {
onExpandedChange(false)
context.openUri(supportUri)
vm.viewModelScope.launchTry {
val newUrl = vm.inputSubsLinkOption.getResult(initValue = it)
newUrl ?: return@launchTry
vm.addOrModifySubs(newUrl, subItem)
}
}
)
}

View File

@ -1,6 +1,5 @@
package li.songe.gkd.ui.home
import android.webkit.URLUtil
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.blankj.utilcode.util.LogUtils
@ -17,6 +16,7 @@ import li.songe.gkd.appScope
import li.songe.gkd.data.RawSubscription
import li.songe.gkd.data.SubsItem
import li.songe.gkd.db.DbSet
import li.songe.gkd.ui.component.InputSubsLinkOption
import li.songe.gkd.util.SortTypeOption
import li.songe.gkd.util.appInfoCacheFlow
import li.songe.gkd.util.clickCountFlow
@ -64,17 +64,12 @@ class HomeVm : ViewModel() {
}.stateIn(appScope, SharingStarted.Eagerly, "")
}
fun addSubsFromUrl(url: String) = viewModelScope.launchTry(Dispatchers.IO) {
fun addOrModifySubs(
url: String,
oldItem: SubsItem? = null,
) = viewModelScope.launchTry(Dispatchers.IO) {
if (subsRefreshingFlow.value) return@launchTry
if (!URLUtil.isNetworkUrl(url)) {
toast("非法链接")
return@launchTry
}
val subItems = subsItemsFlow.value
if (subItems.any { it.updateUrl == url }) {
toast("订阅链接已存在")
return@launchTry
}
subsRefreshingFlow.value = true
try {
val text = try {
@ -93,22 +88,34 @@ class HomeVm : ViewModel() {
toast("解析订阅文件失败")
return@launchTry
}
if (subItems.any { it.id == newSubsRaw.id }) {
toast("订阅已存在")
return@launchTry
if (oldItem == null) {
if (subItems.any { it.id == newSubsRaw.id }) {
toast("订阅已存在")
return@launchTry
}
} else {
if (oldItem.id != newSubsRaw.id) {
toast("订阅id不对应")
return@launchTry
}
}
if (newSubsRaw.id < 0) {
toast("订阅id不可为${newSubsRaw.id}\n负数id为内部使用")
return@launchTry
}
val newItem = SubsItem(
val newItem = oldItem?.copy(updateUrl = url) ?: SubsItem(
id = newSubsRaw.id,
updateUrl = url,
order = if (subItems.isEmpty()) 1 else (subItems.maxBy { it.order }.order + 1)
)
updateSubscription(newSubsRaw)
DbSet.subsItemDao.insert(newItem)
toast("成功添加订阅")
if (oldItem == null) {
DbSet.subsItemDao.insert(newItem)
toast("成功添加订阅")
} else {
DbSet.subsItemDao.update(newItem)
toast("成功修改订阅")
}
} finally {
subsRefreshingFlow.value = false
}
@ -167,4 +174,6 @@ class HomeVm : ViewModel() {
}.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
val showShareDataIdsFlow = MutableStateFlow<Set<Long>?>(null)
val inputSubsLinkOption = InputSubsLinkOption()
}

View File

@ -1,7 +1,6 @@
package li.songe.gkd.ui.home
import android.content.Intent
import android.webkit.URLUtil
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
@ -32,7 +31,6 @@ import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
@ -77,7 +75,6 @@ import li.songe.gkd.util.SafeR
import li.songe.gkd.util.UpdateTimeOption
import li.songe.gkd.util.checkSubsUpdate
import li.songe.gkd.util.findOption
import li.songe.gkd.util.isSafeUrl
import li.songe.gkd.util.launchAsFn
import li.songe.gkd.util.launchTry
import li.songe.gkd.util.map
@ -111,9 +108,6 @@ fun useSubsManagePage(): ScaffoldExt {
orderSubItems = subItems
}
var showAddLinkDialog by remember { mutableStateOf(false) }
var link by remember { mutableStateOf("") }
val refreshing by subsRefreshingFlow.collectAsState()
val pullRefreshState = rememberPullRefreshState(refreshing, { checkSubsUpdate(true) })
var isSelectedMode by remember { mutableStateOf(false) }
@ -135,59 +129,6 @@ fun useSubsManagePage(): ScaffoldExt {
}
}
LaunchedEffect(showAddLinkDialog) {
if (!showAddLinkDialog) {
link = ""
}
}
if (showAddLinkDialog) {
AlertDialog(title = { Text(text = "添加订阅") }, text = {
OutlinedTextField(
value = link,
onValueChange = { link = it.trim() },
maxLines = 8,
modifier = Modifier.fillMaxWidth(),
placeholder = {
Text(text = "请输入订阅链接")
},
isError = link.isNotEmpty() && !URLUtil.isNetworkUrl(link),
)
}, onDismissRequest = {
if (link.isEmpty()) {
showAddLinkDialog = false
}
}, dismissButton = {
TextButton(onClick = {
showAddLinkDialog = false
}) {
Text(text = "取消")
}
}, confirmButton = {
TextButton(enabled = link.isNotBlank(), onClick = {
if (!URLUtil.isNetworkUrl(link)) {
toast("非法链接")
return@TextButton
}
if (subItems.any { s -> s.updateUrl == link }) {
toast("链接已存在")
return@TextButton
}
vm.viewModelScope.launchTry {
if (!isSafeUrl(link)) {
context.mainVm.dialogFlow.waitResult(
title = "未知来源",
text = "你正在添加一个未验证的远程订阅\n\n这可能含有恶意的规则\n\n是否仍然确认添加?"
)
}
showAddLinkDialog = false
vm.addSubsFromUrl(url = link)
}
}) {
Text(text = "确认")
}
})
}
var showSettingsDlg by remember { mutableStateOf(false) }
if (showSettingsDlg) {
AlertDialog(
@ -212,6 +153,7 @@ fun useSubsManagePage(): ScaffoldExt {
}
ShareDataDialog(vm)
vm.inputSubsLinkOption.ContentDialog()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
return ScaffoldExt(
@ -389,7 +331,10 @@ fun useSubsManagePage(): ScaffoldExt {
toast("正在刷新订阅,请稍后操作")
return@FloatingActionButton
}
showAddLinkDialog = true
vm.viewModelScope.launchTry {
val url = vm.inputSubsLinkOption.getResult() ?: return@launchTry
vm.addOrModifySubs(url)
}
}) {
Icon(
imageVector = Icons.Filled.Add,