From 3e446ba995fea19c8f1f26df2ee45c08e8736cd4 Mon Sep 17 00:00:00 2001 From: lisonge Date: Fri, 19 Jan 2024 15:31:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E8=AE=A2=E9=98=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/li/songe/gkd/MainActivity.kt | 6 +- app/src/main/kotlin/li/songe/gkd/db/DbSet.kt | 52 +--------- .../kotlin/li/songe/gkd/ui/SubsManagePage.kt | 99 ++++--------------- .../kotlin/li/songe/gkd/ui/SubsManageVm.kt | 2 + .../li/songe/gkd/ui/component/SubsItemCard.kt | 38 +++---- .../kotlin/li/songe/gkd/util/Constants.kt | 23 ++++- 6 files changed, 60 insertions(+), 160 deletions(-) diff --git a/app/src/main/kotlin/li/songe/gkd/MainActivity.kt b/app/src/main/kotlin/li/songe/gkd/MainActivity.kt index 4c79a6b6d..2a592495b 100644 --- a/app/src/main/kotlin/li/songe/gkd/MainActivity.kt +++ b/app/src/main/kotlin/li/songe/gkd/MainActivity.kt @@ -52,9 +52,6 @@ class MainActivity : CompositionActivity({ val navController = rememberNavController() AppTheme { - ConfirmDialog() - AuthDialog() - UpgradeDialog() CompositionLocalProvider( LocalLauncher provides launcher, LocalPickContentLauncher provides pickContentLauncher, @@ -65,6 +62,9 @@ class MainActivity : CompositionActivity({ navGraph = NavGraphs.root, navController = navController, modifier = Modifier ) } + ConfirmDialog() + AuthDialog() + UpgradeDialog() } } }) diff --git a/app/src/main/kotlin/li/songe/gkd/db/DbSet.kt b/app/src/main/kotlin/li/songe/gkd/db/DbSet.kt index b65ca3350..d1ec4c5eb 100644 --- a/app/src/main/kotlin/li/songe/gkd/db/DbSet.kt +++ b/app/src/main/kotlin/li/songe/gkd/db/DbSet.kt @@ -1,69 +1,19 @@ package li.songe.gkd.db import androidx.room.Room -import androidx.room.RoomDatabase -import androidx.sqlite.db.SupportSQLiteDatabase -import com.blankj.utilcode.util.LogUtils -import io.ktor.client.request.get -import io.ktor.client.statement.bodyAsText -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.withTimeout import li.songe.gkd.app -import li.songe.gkd.appScope -import li.songe.gkd.data.RawSubscription -import li.songe.gkd.data.SubsItem -import li.songe.gkd.util.DEFAULT_SUBS_UPDATE_URL -import li.songe.gkd.util.client import li.songe.gkd.util.dbFolder -import li.songe.gkd.util.launchTry -import li.songe.gkd.util.updateSubscription import java.io.File object DbSet { private val appDb by lazy { Room.databaseBuilder( app, AppDb::class.java, File(dbFolder, "gkd.db").absolutePath - ).addCallback(createCallback()).fallbackToDestructiveMigration().build() + ).fallbackToDestructiveMigration().build() } val subsItemDao by lazy { appDb.subsItemDao() } val subsConfigDao by lazy { appDb.subsConfigDao() } val snapshotDao by lazy { appDb.snapshotDao() } val clickLogDao by lazy { appDb.clickLogDao() } val categoryConfigDao by lazy { appDb.categoryConfigDao() } - - private fun createCallback(): RoomDatabase.Callback { - return object : RoomDatabase.Callback() { - override fun onCreate(db: SupportSQLiteDatabase) { - super.onCreate(db) - appScope.launchTry(Dispatchers.IO) { - delay(3000) - if (subsItemDao.queryById(0).firstOrNull() != null) { - return@launchTry - } - val defaultSubsItem = SubsItem( - id = 0, - order = 0, - updateUrl = DEFAULT_SUBS_UPDATE_URL, - mtime = System.currentTimeMillis() - ) - subsItemDao.insert(defaultSubsItem) - val newSubsRaw = try { - withTimeout(3000) { - RawSubscription.parse( - client.get(defaultSubsItem.updateUrl!!).bodyAsText() - ) - } - } catch (e: Exception) { - LogUtils.d("获取默认订阅失败") - LogUtils.d(e) - return@launchTry - } - updateSubscription(newSubsRaw) - subsItemDao.update(defaultSubsItem.copy(mtime = System.currentTimeMillis())) - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/kotlin/li/songe/gkd/ui/SubsManagePage.kt b/app/src/main/kotlin/li/songe/gkd/ui/SubsManagePage.kt index 674923e88..cc7052ae3 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SubsManagePage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SubsManagePage.kt @@ -53,16 +53,15 @@ import androidx.lifecycle.viewModelScope import com.blankj.utilcode.util.ClipboardUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import li.songe.gkd.data.RawSubscription import li.songe.gkd.data.SubsItem import li.songe.gkd.db.DbSet import li.songe.gkd.ui.component.SubsItemCard +import li.songe.gkd.ui.component.getDialogResult import li.songe.gkd.ui.destinations.CategoryPageDestination import li.songe.gkd.ui.destinations.GlobalRulePageDestination import li.songe.gkd.ui.destinations.SubsPageDestination -import li.songe.gkd.util.DEFAULT_SUBS_UPDATE_URL import li.songe.gkd.util.LocalNavController -import li.songe.gkd.util.formatTimeAgo +import li.songe.gkd.util.isSafeUrl import li.songe.gkd.util.launchAsFn import li.songe.gkd.util.launchTry import li.songe.gkd.util.navigate @@ -102,15 +101,9 @@ fun SubsManagePage() { var deleteSubItem: SubsItem? by remember { mutableStateOf(null) } var menuSubItem: SubsItem? by remember { mutableStateOf(null) } - var showAddDialog by remember { mutableStateOf(false) } var showAddLinkDialog by remember { mutableStateOf(false) } var link by remember { mutableStateOf("") } - val (showSubsRaw, setShowSubsRaw) = remember { - mutableStateOf(null) - } - - val refreshing by vm.refreshingFlow.collectAsState() val pullRefreshState = rememberPullRefreshState(refreshing, vm::refreshSubs) @@ -137,10 +130,10 @@ fun SubsManagePage() { Scaffold( floatingActionButton = { FloatingActionButton(onClick = { - if (subItems.any { it.id == 0L }) { + if (!vm.refreshingFlow.value) { showAddLinkDialog = true } else { - showAddDialog = true + toast("正在刷新订阅,请稍后添加") } }) { Icon( @@ -182,8 +175,10 @@ fun SubsManagePage() { subsItem = subItem, rawSubscription = subsIdToRaw[subItem.id], index = index + 1, - onCheckedChange = vm.viewModelScope.launchAsFn { - DbSet.subsItemDao.update(subItem.copy(enable = it)) + onCheckedChange = { checked -> + vm.viewModelScope.launch { + DbSet.subsItemDao.update(subItem.copy(enable = checked)) + } }, ) } @@ -199,7 +194,6 @@ fun SubsManagePage() { } menuSubItem?.let { menuSubItemVal -> - Dialog(onDismissRequest = { menuSubItem = null }) { Card( modifier = Modifier @@ -308,37 +302,6 @@ fun SubsManagePage() { }) } - if (showAddDialog) { - Dialog(onDismissRequest = { showAddDialog = false }) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - shape = RoundedCornerShape(16.dp), - ) { - Column { - Text(text = "导入默认订阅", modifier = Modifier - .clickable { - showAddDialog = false - vm.addSubsFromUrl(DEFAULT_SUBS_UPDATE_URL) - } - .fillMaxWidth() - .padding(16.dp)) - Divider() - Text(text = "导入其它订阅", modifier = Modifier - .clickable { - showAddDialog = false - showAddLinkDialog = true - } - .fillMaxWidth() - .padding(16.dp)) - } - } - } - } - - - LaunchedEffect(showAddLinkDialog) { if (!showAddLinkDialog) { link = "" @@ -363,43 +326,19 @@ fun SubsManagePage() { toast("链接已存在") return@TextButton } - showAddLinkDialog = false - vm.addSubsFromUrl(url = link) - }) { - Text(text = "添加") - } - }) - } - - if (showSubsRaw != null) { - AlertDialog(onDismissRequest = { setShowSubsRaw(null) }, title = { - Text(text = "订阅详情") - }, text = { - Column( - verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier - ) { - Text(text = "名称: " + showSubsRaw.name) - Text(text = "版本: " + showSubsRaw.version) - if (showSubsRaw.author != null) { - Text(text = "作者: " + showSubsRaw.author) - } - val apps = showSubsRaw.apps - val groupsSize = apps.sumOf { it.groups.size } - if (groupsSize > 0) { - Text(text = "规则: ${apps.size}应用/${groupsSize}规则组") + vm.viewModelScope.launch { + if (!isSafeUrl(link)) { + val result = getDialogResult( + "未知来源", + "你正在添加一个未验证的远程订阅\n\n这可能含有恶意的规则\n\n是否仍然确认添加?" + ) + if (!result) return@launch + } + showAddLinkDialog = false + vm.addSubsFromUrl(url = link) } - - Text( - text = "更新: " + formatTimeAgo( - subItems.find { s -> s.id == showSubsRaw.id }?.mtime ?: 0 - ) - ) - } - }, confirmButton = { - TextButton(onClick = { - setShowSubsRaw(null) }) { - Text(text = "关闭") + Text(text = "添加") } }) } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/SubsManageVm.kt b/app/src/main/kotlin/li/songe/gkd/ui/SubsManageVm.kt index e714d652c..3493c3785 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SubsManageVm.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SubsManageVm.kt @@ -46,6 +46,7 @@ class SubsManageVm @Inject constructor() : ViewModel() { client.get(url).bodyAsText() } catch (e: Exception) { e.printStackTrace() + LogUtils.d(e) toast("下载订阅文件失败") return@launchTry } @@ -53,6 +54,7 @@ class SubsManageVm @Inject constructor() : ViewModel() { RawSubscription.parse(text) } catch (e: Exception) { e.printStackTrace() + LogUtils.d(e) toast("解析订阅文件失败") return@launchTry } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/component/SubsItemCard.kt b/app/src/main/kotlin/li/songe/gkd/ui/component/SubsItemCard.kt index 1d550407c..288d605b7 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/component/SubsItemCard.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/component/SubsItemCard.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.unit.sp import li.songe.gkd.data.RawSubscription import li.songe.gkd.data.SubsItem import li.songe.gkd.util.formatTimeAgo -import li.songe.gkd.util.safeRemoteBaseUrls +import li.songe.gkd.util.isSafeUrl @Composable @@ -42,30 +42,24 @@ fun SubsItemCard( } Spacer(modifier = Modifier.height(5.dp)) Row { - Text( - text = formatTimeAgo(subsItem.mtime), - maxLines = 1, - softWrap = false, - overflow = TextOverflow.Ellipsis, - fontSize = 14.sp - ) - Spacer(modifier = Modifier.width(10.dp)) val sourceText = if (subsItem.id < 0) { "本地来源" - } else if (subsItem.updateUrl != null && safeRemoteBaseUrls.any { s -> - subsItem.updateUrl.startsWith( - s - ) - }) { + } else if (subsItem.updateUrl != null && isSafeUrl(subsItem.updateUrl)) { "可信来源" } else { "未知来源" } Text(text = sourceText, fontSize = 14.sp) - } - Spacer(modifier = Modifier.height(5.dp)) - Row { + Spacer(modifier = Modifier.width(10.dp)) + Text( + text = formatTimeAgo(subsItem.mtime), + maxLines = 1, + softWrap = false, + overflow = TextOverflow.Ellipsis, + fontSize = 14.sp + ) + Spacer(modifier = Modifier.width(10.dp)) if (subsItem.id >= 0) { Text( text = "v" + (rawSubscription.version.toString()), @@ -76,12 +70,12 @@ fun SubsItemCard( ) Spacer(modifier = Modifier.width(10.dp)) } - - Text( - text = rawSubscription.numText, - fontSize = 14.sp - ) } + Spacer(modifier = Modifier.height(5.dp)) + Text( + text = rawSubscription.numText, + fontSize = 14.sp + ) } else { Text( text = "本地无订阅文件,请下拉刷新", diff --git a/app/src/main/kotlin/li/songe/gkd/util/Constants.kt b/app/src/main/kotlin/li/songe/gkd/util/Constants.kt index cc0ec8d6c..6ecaa4693 100644 --- a/app/src/main/kotlin/li/songe/gkd/util/Constants.kt +++ b/app/src/main/kotlin/li/songe/gkd/util/Constants.kt @@ -1,5 +1,7 @@ package li.songe.gkd.util +import android.webkit.URLUtil +import io.ktor.http.Url import li.songe.gkd.BuildConfig const val VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION" @@ -7,8 +9,6 @@ const val VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION" const val FILE_UPLOAD_URL = "https://u.gkd.li/" const val IMPORT_BASE_URL = "https://i.gkd.li/import/" -const val DEFAULT_SUBS_UPDATE_URL = - "https://registry.npmmirror.com/@gkd-kit/subscription/latest/files" const val UPDATE_URL = "https://registry.npmmirror.com/@gkd-kit/app/latest/files/index.json" const val SERVER_SCRIPT_URL = @@ -16,6 +16,8 @@ const val SERVER_SCRIPT_URL = const val REPOSITORY_URL = "https://github.com/gkd-kit/gkd" +const val HOME_PAGE_URL = "https://gkd.li/" + @Suppress("SENSELESS_COMPARISON") val GIT_COMMIT_URL = if (BuildConfig.GIT_COMMIT_ID != null) { "https://github.com/gkd-kit/gkd/commit/${BuildConfig.GIT_COMMIT_ID}" @@ -23,11 +25,24 @@ val GIT_COMMIT_URL = if (BuildConfig.GIT_COMMIT_ID != null) { null } -val safeRemoteBaseUrls = arrayOf( +private val safeRemoteBaseUrls = arrayOf( "https://registry.npmmirror.com/@gkd-kit/", "https://cdn.jsdelivr.net/npm/@gkd-kit/", "https://fastly.jsdelivr.net/npm/@gkd-kit/", "https://unpkg.com/@gkd-kit/", "https://github.com/gkd-kit/", - "https://raw.githubusercontent.com/gkd-kit/" + "https://raw.githubusercontent.com/gkd-kit/", + HOME_PAGE_URL, ) + +fun isSafeUrl(url: String): Boolean { + if (!URLUtil.isHttpsUrl(url)) return false + if (safeRemoteBaseUrls.any { u -> url.startsWith(u) }) { + return true + } + return try { + Url(url).host.endsWith(".gkd.li") + } catch (e: Exception) { + false + } +}