From 3d10cfe45a7a6c9ae4f4f6d31da6f2844b7e9619 Mon Sep 17 00:00:00 2001 From: lisonge Date: Thu, 22 Feb 2024 22:34:21 +0800 Subject: [PATCH] perf: some changes --- app/build.gradle.kts | 4 + .../main/kotlin/li/songe/gkd/data/ClickLog.kt | 4 + .../kotlin/li/songe/gkd/ui/AppItemPage.kt | 78 ++--- .../kotlin/li/songe/gkd/ui/CategoryPage.kt | 106 +++--- .../kotlin/li/songe/gkd/ui/ClickLogPage.kt | 325 +++++++++--------- .../main/kotlin/li/songe/gkd/ui/ClickLogVm.kt | 34 +- .../li/songe/gkd/ui/GlobalRuleExcludePage.kt | 2 +- .../kotlin/li/songe/gkd/ui/GlobalRulePage.kt | 70 ++-- .../kotlin/li/songe/gkd/ui/SnapshotPage.kt | 300 ++++++++-------- .../main/kotlin/li/songe/gkd/ui/SubsPage.kt | 69 ++-- .../li/songe/gkd/ui/home/AppListPage.kt | 13 +- .../li/songe/gkd/ui/home/SettingsPage.kt | 160 +++++---- .../li/songe/gkd/ui/home/SubsManagePage.kt | 187 +++++----- settings.gradle.kts | 7 +- 14 files changed, 692 insertions(+), 667 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0e50aa7ef5..81b9e892df 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -182,8 +182,12 @@ dependencies { implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.ktx) + implementation(libs.androidx.room.paging) ksp(libs.androidx.room.compiler) + implementation(libs.androidx.paging.runtime) + implementation(libs.androidx.paging.compose) + implementation(libs.ktor.server.core) implementation(libs.ktor.server.cio) implementation(libs.ktor.server.content.negotiation) diff --git a/app/src/main/kotlin/li/songe/gkd/data/ClickLog.kt b/app/src/main/kotlin/li/songe/gkd/data/ClickLog.kt index f23286fc5f..42ed8978db 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/ClickLog.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/ClickLog.kt @@ -1,6 +1,7 @@ package li.songe.gkd.data import android.os.Parcelable +import androidx.paging.PagingSource import androidx.room.ColumnInfo import androidx.room.Dao import androidx.room.Delete @@ -49,6 +50,9 @@ data class ClickLog( @Query("SELECT * FROM click_log ORDER BY id DESC LIMIT 1000") fun query(): Flow> + @Query("SELECT * FROM click_log ORDER BY id DESC ") + fun pagingSource(): PagingSource + @Query("SELECT COUNT(*) FROM click_log") fun count(): Flow diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt index 9b906c2d19..c84d61a314 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt @@ -295,53 +295,51 @@ fun AppItemPage( .padding(16.dp), shape = RoundedCornerShape(16.dp), ) { - Column { - Text(text = "编辑禁用", modifier = Modifier + Text(text = "编辑禁用", modifier = Modifier + .clickable { + setExcludeGroupRaw(menuGroupRaw) + setMenuGroupRaw(null) + } + .padding(16.dp) + .fillMaxWidth()) + if (editable) { + Text(text = "编辑规则组", modifier = Modifier .clickable { - setExcludeGroupRaw(menuGroupRaw) + setEditGroupRaw(menuGroupRaw) setMenuGroupRaw(null) } .padding(16.dp) .fillMaxWidth()) - if (editable) { - Text(text = "编辑规则组", modifier = Modifier - .clickable { - setEditGroupRaw(menuGroupRaw) - setMenuGroupRaw(null) - } - .padding(16.dp) - .fillMaxWidth()) - Text(text = "删除规则组", modifier = Modifier - .clickable { - vm.viewModelScope.launchTry(Dispatchers.IO) { - subsRaw ?: return@launchTry - val newSubsRaw = subsRaw.copy( - apps = subsRaw.apps - .toMutableList() - .apply { - set( - indexOfFirst { a -> a.id == appRawVal.id }, - appRawVal.copy( - groups = appRawVal.groups - .filter { g -> g.key != menuGroupRaw.key } - ) + Text(text = "删除规则组", modifier = Modifier + .clickable { + vm.viewModelScope.launchTry(Dispatchers.IO) { + subsRaw ?: return@launchTry + val newSubsRaw = subsRaw.copy( + apps = subsRaw.apps + .toMutableList() + .apply { + set( + indexOfFirst { a -> a.id == appRawVal.id }, + appRawVal.copy( + groups = appRawVal.groups + .filter { g -> g.key != menuGroupRaw.key } ) - } - ) - updateSubscription(newSubsRaw) - DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis())) - DbSet.subsConfigDao.delete( - subsItemVal.id, appRawVal.id, menuGroupRaw.key - ) - toast("删除成功") - setMenuGroupRaw(null) - } + ) + } + ) + updateSubscription(newSubsRaw) + DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis())) + DbSet.subsConfigDao.delete( + subsItemVal.id, appRawVal.id, menuGroupRaw.key + ) + toast("删除成功") + setMenuGroupRaw(null) } - .padding(16.dp) - .fillMaxWidth(), - color = MaterialTheme.colorScheme.error - ) - } + } + .padding(16.dp) + .fillMaxWidth(), + color = MaterialTheme.colorScheme.error + ) } } } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/CategoryPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/CategoryPage.kt index 75b67a8669..cfb7bc9a0b 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/CategoryPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/CategoryPage.kt @@ -181,37 +181,35 @@ fun CategoryPage(subsItemId: Long) { .padding(16.dp), shape = RoundedCornerShape(16.dp), ) { - Column { - enableGroupRadioOptions.forEach { option -> - val onClick: () -> Unit = { - vm.viewModelScope.launchTry(Dispatchers.IO) { - DbSet.categoryConfigDao.insert( - (categoryConfig ?: CategoryConfig( - enable = option.second, - subsItemId = subsItemId, - categoryKey = category.key - )).copy(enable = option.second) - ) - } + enableGroupRadioOptions.forEach { option -> + val onClick: () -> Unit = { + vm.viewModelScope.launchTry(Dispatchers.IO) { + DbSet.categoryConfigDao.insert( + (categoryConfig ?: CategoryConfig( + enable = option.second, + subsItemId = subsItemId, + categoryKey = category.key + )).copy(enable = option.second) + ) } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .selectable( - selected = (option.second == enable), - onClick = onClick - ) - .padding(horizontal = 16.dp) - ) { - RadioButton( + } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .selectable( selected = (option.second == enable), onClick = onClick ) - Text( - text = option.first, modifier = Modifier.padding(start = 16.dp) - ) - } + .padding(horizontal = 16.dp) + ) { + RadioButton( + selected = (option.second == enable), + onClick = onClick + ) + Text( + text = option.first, modifier = Modifier.padding(start = 16.dp) + ) } } } @@ -328,36 +326,34 @@ fun CategoryPage(subsItemId: Long) { .padding(16.dp), shape = RoundedCornerShape(16.dp), ) { - Column { - Text(text = "编辑", modifier = Modifier - .clickable { - setEditNameCategory(menuCategory) - setMenuCategory(null) - } - .padding(16.dp) - .fillMaxWidth()) - Text(text = "删除", modifier = Modifier - .clickable { - vm.viewModelScope.launchTry(Dispatchers.IO) { - subsItem?.apply { - updateSubscription(subsRawVal.copy( - categories = subsRawVal.categories.filter { c -> c.key != menuCategory.key } - )) - DbSet.subsItemDao.update(copy(mtime = System.currentTimeMillis())) - } - DbSet.categoryConfigDao.deleteByCategoryKey( - subsItemId, - menuCategory.key - ) - toast("删除成功") - setMenuCategory(null) + Text(text = "编辑", modifier = Modifier + .clickable { + setEditNameCategory(menuCategory) + setMenuCategory(null) + } + .padding(16.dp) + .fillMaxWidth()) + Text(text = "删除", modifier = Modifier + .clickable { + vm.viewModelScope.launchTry(Dispatchers.IO) { + subsItem?.apply { + updateSubscription(subsRawVal.copy( + categories = subsRawVal.categories.filter { c -> c.key != menuCategory.key } + )) + DbSet.subsItemDao.update(copy(mtime = System.currentTimeMillis())) } + DbSet.categoryConfigDao.deleteByCategoryKey( + subsItemId, + menuCategory.key + ) + toast("删除成功") + setMenuCategory(null) } - .padding(16.dp) - .fillMaxWidth(), - color = MaterialTheme.colorScheme.error - ) - } + } + .padding(16.dp) + .fillMaxWidth(), + color = MaterialTheme.colorScheme.error + ) } } } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/ClickLogPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/ClickLogPage.kt index 54fec3cbfe..43025e31b3 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/ClickLogPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/ClickLogPage.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -33,15 +32,18 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewModelScope +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemKey +import com.blankj.utilcode.util.LogUtils import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootNavGraph import kotlinx.coroutines.Dispatchers @@ -75,8 +77,9 @@ fun ClickLogPage() { val navController = LocalNavController.current val vm = hiltViewModel() - val clickDataList by vm.clickDataListFlow.collectAsState() +// val clickDataList by vm.clickDataListFlow.collectAsState() val clickLogCount by vm.clickLogCountFlow.collectAsState() + val clickDataItems = vm.pagingDataFlow.collectAsLazyPagingItems() val appInfoCache by appInfoCacheFlow.collectAsState() val subsIdToRaw by subsIdToRawFlow.collectAsState() @@ -105,6 +108,10 @@ fun ClickLogPage() { mutableStateOf(false) } + LaunchedEffect(key1 = clickDataItems.itemSnapshotList.items, block = { + LogUtils.d(clickDataItems.itemSnapshotList.items.size) + }) + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { TopAppBar( @@ -121,7 +128,7 @@ fun ClickLogPage() { }, title = { Text(text = "触发记录" + if (clickLogCount <= 0) "" else ("-$clickLogCount")) }, actions = { - if (clickDataList.isNotEmpty()) { + if (clickLogCount > 0) { IconButton(onClick = { showDeleteDlg = true }) { Icon( imageVector = Icons.Outlined.Delete, @@ -131,71 +138,70 @@ fun ClickLogPage() { } }) }, content = { contentPadding -> - if (clickDataList.isNotEmpty()) { - LazyColumn( - modifier = Modifier.padding(contentPadding), - ) { - items(clickDataList, { it.t0.id }) { (clickLog, group, rule) -> - Column(modifier = Modifier - .clickable { - previewClickLog = clickLog - } - .fillMaxWidth() - .padding(10.dp)) { - Row { - Text( - text = clickLog.id.format("MM-dd HH:mm:ss"), - fontFamily = FontFamily.Monospace - ) - Spacer(modifier = Modifier.width(10.dp)) - Text( - text = appInfoCache[clickLog.appId]?.name ?: clickLog.appId ?: "" - ) - } + LazyColumn( + modifier = Modifier.padding(contentPadding), + ) { + items( + count = clickDataItems.itemCount, + key = clickDataItems.itemKey { c -> c.t0.id } + ) { i -> + val (clickLog, group, rule) = clickDataItems[i] ?: return@items + Column(modifier = Modifier + .clickable { + previewClickLog = clickLog + } + .fillMaxWidth() + .padding(10.dp)) { + Row { + Text( + text = clickLog.id.format("MM-dd HH:mm:ss"), + fontFamily = FontFamily.Monospace + ) Spacer(modifier = Modifier.width(10.dp)) - val showActivityId = if (clickLog.activityId != null) { - if (clickLog.appId != null && clickLog.activityId.startsWith( - clickLog.appId - ) - ) { - clickLog.activityId.substring(clickLog.appId.length) - } else { - clickLog.activityId - } - } else { - null - } - if (showActivityId != null) { - Text( - text = showActivityId, - overflow = TextOverflow.Ellipsis, - maxLines = 1, + Text( + text = appInfoCache[clickLog.appId]?.name ?: clickLog.appId ?: "" + ) + } + Spacer(modifier = Modifier.width(10.dp)) + val showActivityId = if (clickLog.activityId != null) { + if (clickLog.appId != null && clickLog.activityId.startsWith( + clickLog.appId ) + ) { + clickLog.activityId.substring(clickLog.appId.length) + } else { + clickLog.activityId } - if (group?.name != null) { - Text(text = group.name) - } - if (rule?.name != null) { - Text(text = rule.name ?: "") - } else if ((group?.rules?.size ?: 0) > 1) { - Text(text = (if (clickLog.ruleKey != null) "key=${clickLog.ruleKey}, " else "") + "index=${clickLog.ruleIndex}") - } + } else { + null + } + if (showActivityId != null) { + Text( + text = showActivityId, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + } + if (group?.name != null) { + Text(text = group.name) + } + if (rule?.name != null) { + Text(text = rule.name ?: "") + } else if ((group?.rules?.size ?: 0) > 1) { + Text(text = (if (clickLog.ruleKey != null) "key=${clickLog.ruleKey}, " else "") + "index=${clickLog.ruleIndex}") } - HorizontalDivider() - } - item { - Spacer(modifier = Modifier.height(10.dp)) } + HorizontalDivider() } - } else { - Column( - modifier = Modifier - .padding(contentPadding) - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { + item { Spacer(modifier = Modifier.height(40.dp)) - Text(text = "暂无记录") + if (clickLogCount == 0) { + Text( + text = "暂无记录", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + } } } }) @@ -214,124 +220,121 @@ fun ClickLogPage() { } val appInfo = appInfoCache[clickLog.appId] - Column { - Text(text = "查看规则组", modifier = Modifier - .clickable { - clickLog.appId ?: return@clickable - if (clickLog.groupType == SubsConfig.AppGroupType) { - navController.navigate( - AppItemPageDestination( - clickLog.subsId, clickLog.appId, clickLog.groupKey - ) + Text(text = "查看规则组", modifier = Modifier + .clickable { + clickLog.appId ?: return@clickable + if (clickLog.groupType == SubsConfig.AppGroupType) { + navController.navigate( + AppItemPageDestination( + clickLog.subsId, clickLog.appId, clickLog.groupKey ) - } else if (clickLog.groupType == SubsConfig.GlobalGroupType) { - navController.navigate( - GlobalRulePageDestination( - clickLog.subsId, clickLog.groupKey - ) - ) - } - previewClickLog = null - } - .fillMaxWidth() - .padding(16.dp)) - if (clickLog.groupType == SubsConfig.GlobalGroupType && clickLog.appId != null) { - val group = - subsIdToRaw[clickLog.subsId]?.globalGroups?.find { g -> g.key == clickLog.groupKey } - val appChecked = if (group != null) { - getChecked( - oldExclude, - group, - clickLog.appId, - appInfo ) - } else { - null - } - if (appChecked != null) { - Text( - text = if (appChecked) "在此应用禁用" else "移除在此应用的禁用", - modifier = Modifier - .clickable( - onClick = vm.viewModelScope.launchAsFn( - Dispatchers.IO - ) { - val subsConfig = previewConfig ?: SubsConfig( - type = SubsConfig.GlobalGroupType, - subsItemId = clickLog.subsId, - groupKey = clickLog.groupKey, - ) - val newSubsConfig = subsConfig.copy( - exclude = oldExclude - .copy( - appIds = oldExclude.appIds - .toMutableMap() - .apply { - set(clickLog.appId, appChecked) - }) - .stringify() - ) - DbSet.subsConfigDao.insert(newSubsConfig) - toast("更新禁用") - }) - .fillMaxWidth() - .padding(16.dp), + } else if (clickLog.groupType == SubsConfig.GlobalGroupType) { + navController.navigate( + GlobalRulePageDestination( + clickLog.subsId, clickLog.groupKey + ) ) } + previewClickLog = null + } + .fillMaxWidth() + .padding(16.dp)) + if (clickLog.groupType == SubsConfig.GlobalGroupType && clickLog.appId != null) { + val group = + subsIdToRaw[clickLog.subsId]?.globalGroups?.find { g -> g.key == clickLog.groupKey } + val appChecked = if (group != null) { + getChecked( + oldExclude, + group, + clickLog.appId, + appInfo + ) + } else { + null } - if (clickLog.appId != null && clickLog.activityId != null) { - val disabled = - oldExclude.activityIds.contains(clickLog.appId to clickLog.activityId) + if (appChecked != null) { Text( - text = if (disabled) "移除在此页面的禁用" else "在此页面禁用", + text = if (appChecked) "在此应用禁用" else "移除在此应用的禁用", modifier = Modifier - .clickable(onClick = vm.viewModelScope.launchAsFn(Dispatchers.IO) { - val subsConfig = - if (clickLog.groupType == SubsConfig.AppGroupType) { - previewConfig ?: SubsConfig( - type = SubsConfig.AppGroupType, - subsItemId = clickLog.subsId, - appId = clickLog.appId, - groupKey = clickLog.groupKey, - ) - } else { - previewConfig ?: SubsConfig( - type = SubsConfig.GlobalGroupType, - subsItemId = clickLog.subsId, - groupKey = clickLog.groupKey, - ) - } - val newSubsConfig = subsConfig.copy( - exclude = oldExclude - .switch( - clickLog.appId, - clickLog.activityId - ) - .stringify() - ) - DbSet.subsConfigDao.insert(newSubsConfig) - toast("更新禁用") - }) + .clickable( + onClick = vm.viewModelScope.launchAsFn( + Dispatchers.IO + ) { + val subsConfig = previewConfig ?: SubsConfig( + type = SubsConfig.GlobalGroupType, + subsItemId = clickLog.subsId, + groupKey = clickLog.groupKey, + ) + val newSubsConfig = subsConfig.copy( + exclude = oldExclude + .copy( + appIds = oldExclude.appIds + .toMutableMap() + .apply { + set(clickLog.appId, appChecked) + }) + .stringify() + ) + DbSet.subsConfigDao.insert(newSubsConfig) + toast("更新禁用") + }) .fillMaxWidth() .padding(16.dp), ) } - + } + if (clickLog.appId != null && clickLog.activityId != null) { + val disabled = + oldExclude.activityIds.contains(clickLog.appId to clickLog.activityId) Text( - text = "删除记录", + text = if (disabled) "移除在此页面的禁用" else "在此页面禁用", modifier = Modifier - .clickable(onClick = scope.launchAsFn { - previewClickLog = null - DbSet.clickLogDao.delete(clickLog) - toast("删除成功") + .clickable(onClick = vm.viewModelScope.launchAsFn(Dispatchers.IO) { + val subsConfig = + if (clickLog.groupType == SubsConfig.AppGroupType) { + previewConfig ?: SubsConfig( + type = SubsConfig.AppGroupType, + subsItemId = clickLog.subsId, + appId = clickLog.appId, + groupKey = clickLog.groupKey, + ) + } else { + previewConfig ?: SubsConfig( + type = SubsConfig.GlobalGroupType, + subsItemId = clickLog.subsId, + groupKey = clickLog.groupKey, + ) + } + val newSubsConfig = subsConfig.copy( + exclude = oldExclude + .switch( + clickLog.appId, + clickLog.activityId + ) + .stringify() + ) + DbSet.subsConfigDao.insert(newSubsConfig) + toast("更新禁用") }) .fillMaxWidth() .padding(16.dp), - color = MaterialTheme.colorScheme.error ) } - } + Text( + text = "删除记录", + modifier = Modifier + .clickable(onClick = scope.launchAsFn { + previewClickLog = null + DbSet.clickLogDao.delete(clickLog) + toast("删除成功") + }) + .fillMaxWidth() + .padding(16.dp), + color = MaterialTheme.colorScheme.error + ) + } } } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/ClickLogVm.kt b/app/src/main/kotlin/li/songe/gkd/ui/ClickLogVm.kt index a56fb41207..a24ea7f2ac 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/ClickLogVm.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/ClickLogVm.kt @@ -2,6 +2,10 @@ package li.songe.gkd.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.cachedIn +import androidx.paging.map import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -15,9 +19,10 @@ import javax.inject.Inject @HiltViewModel class ClickLogVm @Inject constructor() : ViewModel() { - val clickDataListFlow = - combine(DbSet.clickLogDao.query(), subsIdToRawFlow) { clickLogs, subsIdToRaw -> - clickLogs.map { c -> + val pagingDataFlow = Pager(PagingConfig(pageSize = 100)) { DbSet.clickLogDao.pagingSource() } + .flow.cachedIn(viewModelScope) + .combine(subsIdToRawFlow) { pagingData, subsIdToRaw -> + pagingData.map { c -> val group = if (c.groupType == SubsConfig.AppGroupType) { val app = subsIdToRaw[c.subsId]?.apps?.find { a -> a.id == c.appId } app?.groups?.find { g -> g.key == c.groupKey } @@ -33,7 +38,28 @@ class ClickLogVm @Inject constructor() : ViewModel() { } Tuple3(c, group, rule) } - }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) + } + + +// val clickDataListFlow = +// combine(DbSet.clickLogDao.query(), subsIdToRawFlow) { clickLogs, subsIdToRaw -> +// clickLogs.map { c -> +// val group = if (c.groupType == SubsConfig.AppGroupType) { +// val app = subsIdToRaw[c.subsId]?.apps?.find { a -> a.id == c.appId } +// app?.groups?.find { g -> g.key == c.groupKey } +// } else { +// subsIdToRaw[c.subsId]?.globalGroups?.find { g -> g.key == c.groupKey } +// } +// val rule = group?.rules?.run { +// if (c.ruleKey != null) { +// find { r -> r.key == c.ruleKey } +// } else { +// getOrNull(c.ruleIndex) +// } +// } +// Tuple3(c, group, rule) +// } +// }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) val clickLogCountFlow = DbSet.clickLogDao.count().stateIn(viewModelScope, SharingStarted.Eagerly, 0) diff --git a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludePage.kt b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludePage.kt index 632b37bebd..97494d680a 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludePage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludePage.kt @@ -122,7 +122,7 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) { AppBarTextField( value = searchStr, onValueChange = { newValue -> vm.searchStrFlow.value = newValue.trim() }, - hint = "请输入应用名称", + hint = "请输入应用名称/ID", modifier = Modifier.focusRequester(focusRequester) ) } else { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRulePage.kt b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRulePage.kt index dfbe50d543..ac775713d8 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRulePage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRulePage.kt @@ -272,50 +272,48 @@ fun GlobalRulePage(subsItemId: Long, focusGroupKey: Int? = null) { .padding(16.dp), shape = RoundedCornerShape(16.dp), ) { - Column { - Text(text = "编辑禁用", modifier = Modifier + Text(text = "编辑禁用", modifier = Modifier + .clickable { + setMenuGroupRaw(null) + navController.navigate( + GlobalRuleExcludePageDestination( + subsItemId, + menuGroupRaw.key + ) + ) + } + .padding(16.dp) + .fillMaxWidth()) + if (editable) { + Text(text = "编辑规则组", modifier = Modifier .clickable { + setEditGroupRaw(menuGroupRaw) setMenuGroupRaw(null) - navController.navigate( - GlobalRuleExcludePageDestination( - subsItemId, - menuGroupRaw.key - ) - ) } .padding(16.dp) .fillMaxWidth()) - if (editable) { - Text(text = "编辑规则组", modifier = Modifier - .clickable { - setEditGroupRaw(menuGroupRaw) - setMenuGroupRaw(null) - } - .padding(16.dp) - .fillMaxWidth()) - Text(text = "删除规则组", modifier = Modifier - .clickable { - setMenuGroupRaw(null) - vm.viewModelScope.launchTry { - if (!getDialogResult("是否删除${menuGroupRaw.name}")) return@launchTry - updateSubscription( - rawSubs.copy( - globalGroups = rawSubs.globalGroups.filter { g -> g.key != menuGroupRaw.key } - ) + Text(text = "删除规则组", modifier = Modifier + .clickable { + setMenuGroupRaw(null) + vm.viewModelScope.launchTry { + if (!getDialogResult("是否删除${menuGroupRaw.name}")) return@launchTry + updateSubscription( + rawSubs.copy( + globalGroups = rawSubs.globalGroups.filter { g -> g.key != menuGroupRaw.key } ) - val subsConfig = - subsConfigs.find { it.groupKey == menuGroupRaw.key } - if (subsConfig != null) { - DbSet.subsConfigDao.delete(subsConfig) - } - DbSet.subsItemDao.updateMtime(rawSubs.id) + ) + val subsConfig = + subsConfigs.find { it.groupKey == menuGroupRaw.key } + if (subsConfig != null) { + DbSet.subsConfigDao.delete(subsConfig) } + DbSet.subsItemDao.updateMtime(rawSubs.id) } - .padding(16.dp) - .fillMaxWidth(), - color = MaterialTheme.colorScheme.error - ) - } + } + .padding(16.dp) + .fillMaxWidth(), + color = MaterialTheme.colorScheme.error + ) } } } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/SnapshotPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/SnapshotPage.kt index 8f0bef9ef6..95f867f841 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SnapshotPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SnapshotPage.kt @@ -37,11 +37,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog @@ -123,63 +123,59 @@ fun SnapshotPage() { } }) }, content = { contentPadding -> - if (snapshots.isNotEmpty()) { - LazyColumn( - modifier = Modifier.padding(contentPadding), - ) { - items(snapshots, { it.id }) { snapshot -> - Column(modifier = Modifier - .fillMaxWidth() - .clickable { - selectedSnapshot = snapshot - } - .padding(10.dp)) { - Row { - Text( - text = snapshot.id.format("MM-dd HH:mm:ss"), - fontFamily = FontFamily.Monospace - ) - Spacer(modifier = Modifier.width(10.dp)) - Text( - text = snapshot.appName ?: snapshot.appId ?: snapshot.id.toString(), - overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) - } - if (snapshot.activityId != null) { - val showActivityId = - if (snapshot.appId != null && snapshot.activityId.startsWith( - snapshot.appId - ) - ) { - snapshot.activityId.substring(snapshot.appId.length) - } else { - snapshot.activityId - } - Spacer(modifier = Modifier.width(10.dp)) - Text( - text = showActivityId, overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) - } + LazyColumn( + modifier = Modifier.padding(contentPadding), + ) { + items(snapshots, { it.id }) { snapshot -> + Column(modifier = Modifier + .fillMaxWidth() + .clickable { + selectedSnapshot = snapshot + } + .padding(10.dp)) { + Row { + Text( + text = snapshot.id.format("MM-dd HH:mm:ss"), + fontFamily = FontFamily.Monospace + ) + Spacer(modifier = Modifier.width(10.dp)) + Text( + text = snapshot.appName ?: snapshot.appId ?: snapshot.id.toString(), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + } + if (snapshot.activityId != null) { + val showActivityId = + if (snapshot.appId != null && snapshot.activityId.startsWith( + snapshot.appId + ) + ) { + snapshot.activityId.substring(snapshot.appId.length) + } else { + snapshot.activityId + } + Spacer(modifier = Modifier.width(10.dp)) + Text( + text = showActivityId, overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) } - HorizontalDivider() - } - item { - Spacer(modifier = Modifier.height(10.dp)) } + HorizontalDivider() } - } else { - Column( - modifier = Modifier - .padding(contentPadding) - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { + item { Spacer(modifier = Modifier.height(40.dp)) - Text(text = "暂无记录") + if (snapshots.isEmpty()) { + Text( + text = "暂无记录", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + } } } + }) selectedSnapshot?.let { snapshotVal -> @@ -190,121 +186,119 @@ fun SnapshotPage() { .padding(16.dp), shape = RoundedCornerShape(16.dp), ) { - Column { - val modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - Text( - text = "查看", modifier = Modifier - .clickable(onClick = scope.launchAsFn { - navController.navigate( - ImagePreviewPageDestination( - filePath = snapshotVal.screenshotFile.absolutePath, - title = snapshotVal.appName, - ) + val modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + Text( + text = "查看", modifier = Modifier + .clickable(onClick = scope.launchAsFn { + navController.navigate( + ImagePreviewPageDestination( + filePath = snapshotVal.screenshotFile.absolutePath, + title = snapshotVal.appName, ) + ) + selectedSnapshot = null + }) + .then(modifier) + ) + HorizontalDivider() + Text( + text = "分享", + modifier = Modifier + .clickable(onClick = vm.viewModelScope.launchAsFn { + val zipFile = SnapshotExt.getSnapshotZipFile(snapshotVal.id) + context.shareFile(zipFile, "分享快照文件") + selectedSnapshot = null + }) + .then(modifier) + ) + HorizontalDivider() + if (snapshotVal.githubAssetId != null) { + Text( + text = "复制链接", modifier = Modifier + .clickable(onClick = { selectedSnapshot = null + ClipboardUtils.copyText(IMPORT_BASE_URL + snapshotVal.githubAssetId) + toast("复制成功") }) .then(modifier) ) - HorizontalDivider() + } else { Text( - text = "分享", - modifier = Modifier - .clickable(onClick = vm.viewModelScope.launchAsFn { - val zipFile = SnapshotExt.getSnapshotZipFile(snapshotVal.id) - context.shareFile(zipFile, "分享快照文件") + text = "生成链接(需科学上网)", modifier = Modifier + .clickable(onClick = { selectedSnapshot = null + vm.uploadZip(snapshotVal) }) .then(modifier) ) - HorizontalDivider() - if (snapshotVal.githubAssetId != null) { - Text( - text = "复制链接", modifier = Modifier - .clickable(onClick = { - selectedSnapshot = null - ClipboardUtils.copyText(IMPORT_BASE_URL + snapshotVal.githubAssetId) - toast("复制成功") - }) - .then(modifier) - ) - } else { - Text( - text = "生成链接(需科学上网)", modifier = Modifier - .clickable(onClick = { - selectedSnapshot = null - vm.uploadZip(snapshotVal) - }) - .then(modifier) - ) - } - HorizontalDivider() + } + HorizontalDivider() - Text( - text = "保存截图到相册", - modifier = Modifier - .clickable(onClick = vm.viewModelScope.launchAsFn { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - val isGranted = - requestPermissionLauncher.launchForResult(Manifest.permission.WRITE_EXTERNAL_STORAGE) - if (!isGranted) { - toast("保存失败,暂无权限") - return@launchAsFn - } + Text( + text = "保存截图到相册", + modifier = Modifier + .clickable(onClick = vm.viewModelScope.launchAsFn { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + val isGranted = + requestPermissionLauncher.launchForResult(Manifest.permission.WRITE_EXTERNAL_STORAGE) + if (!isGranted) { + toast("保存失败,暂无权限") + return@launchAsFn } - ImageUtils.save2Album( - ImageUtils.getBitmap(snapshotVal.screenshotFile), - Bitmap.CompressFormat.PNG, - true - ) - toast("保存成功") - selectedSnapshot = null - }) - .then(modifier) - ) - HorizontalDivider() - Text( - text = "替换截图(去除隐私)", - modifier = Modifier - .clickable(onClick = vm.viewModelScope.launchAsFn { - val uri = pickContentLauncher.launchForImageResult() - withContext(Dispatchers.IO) { - val oldBitmap = ImageUtils.getBitmap(snapshotVal.screenshotFile) - val newBytes = UriUtils.uri2Bytes(uri) - val newBitmap = ImageUtils.getBitmap(newBytes, 0) - if (oldBitmap.width == newBitmap.width && oldBitmap.height == newBitmap.height) { - snapshotVal.screenshotFile.writeBytes(newBytes) - File(snapshotZipDir, "${snapshotVal.id}.zip").apply { - if (exists()) delete() - } - if (snapshotVal.githubAssetId != null) { - // 当本地快照变更时, 移除快照链接 - DbSet.snapshotDao.update(snapshotVal.copy(githubAssetId = null)) - } - } else { - toast("截图尺寸不一致,无法替换") - return@withContext + } + ImageUtils.save2Album( + ImageUtils.getBitmap(snapshotVal.screenshotFile), + Bitmap.CompressFormat.PNG, + true + ) + toast("保存成功") + selectedSnapshot = null + }) + .then(modifier) + ) + HorizontalDivider() + Text( + text = "替换截图(去除隐私)", + modifier = Modifier + .clickable(onClick = vm.viewModelScope.launchAsFn { + val uri = pickContentLauncher.launchForImageResult() + withContext(Dispatchers.IO) { + val oldBitmap = ImageUtils.getBitmap(snapshotVal.screenshotFile) + val newBytes = UriUtils.uri2Bytes(uri) + val newBitmap = ImageUtils.getBitmap(newBytes, 0) + if (oldBitmap.width == newBitmap.width && oldBitmap.height == newBitmap.height) { + snapshotVal.screenshotFile.writeBytes(newBytes) + File(snapshotZipDir, "${snapshotVal.id}.zip").apply { + if (exists()) delete() } + if (snapshotVal.githubAssetId != null) { + // 当本地快照变更时, 移除快照链接 + DbSet.snapshotDao.update(snapshotVal.copy(githubAssetId = null)) + } + } else { + toast("截图尺寸不一致,无法替换") + return@withContext } - toast("替换成功") - selectedSnapshot = null - }) - .then(modifier) - ) - HorizontalDivider() - Text( - text = "删除", modifier = Modifier - .clickable(onClick = scope.launchAsFn { - DbSet.snapshotDao.delete(snapshotVal) - withContext(Dispatchers.IO) { - SnapshotExt.removeAssets(snapshotVal.id) - } - selectedSnapshot = null - }) - .then(modifier), color = colorScheme.error - ) - } + } + toast("替换成功") + selectedSnapshot = null + }) + .then(modifier) + ) + HorizontalDivider() + Text( + text = "删除", modifier = Modifier + .clickable(onClick = scope.launchAsFn { + DbSet.snapshotDao.delete(snapshotVal) + withContext(Dispatchers.IO) { + SnapshotExt.removeAssets(snapshotVal.id) + } + selectedSnapshot = null + }) + .then(modifier), color = colorScheme.error + ) } } } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt index f45fc5ace7..e28fb13de8 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt @@ -2,7 +2,6 @@ package li.songe.gkd.ui import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -50,6 +49,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog @@ -145,7 +145,7 @@ fun SubsPage( AppBarTextField( value = searchStr, onValueChange = { newValue -> vm.searchStrFlow.value = newValue.trim() }, - hint = "请输入应用名称", + hint = "请输入应用名称/ID", modifier = Modifier.focusRequester(focusRequester) ) } else { @@ -265,24 +265,15 @@ fun SubsPage( }) } item { + Spacer(modifier = Modifier.height(40.dp)) if (appAndConfigs.isEmpty()) { - Spacer(modifier = Modifier.height(40.dp)) - Column( + Text( + text = if (searchStr.isNotEmpty()) "暂无搜索结果" else "暂无规则", modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - if (searchStr.isNotEmpty()) { - Text(text = "暂无搜索结果") - } else { - Text(text = "暂无规则") - } - } + textAlign = TextAlign.Center + ) } } - item { - Spacer(modifier = Modifier.height(20.dp)) - } - } } @@ -431,31 +422,29 @@ fun SubsPage( .padding(16.dp), shape = RoundedCornerShape(16.dp), ) { - Column { - Text(text = "复制", modifier = Modifier - .clickable { - ClipboardUtils.copyText( - json.encodeToJson5String(menuAppRawVal) - ) - toast("复制成功") - menuRawApp = null - } - .fillMaxWidth() - .padding(16.dp)) - Text(text = "删除", modifier = Modifier - .clickable { - // 也许需要二次确认 - vm.viewModelScope.launchTry(Dispatchers.IO) { - updateSubscription(subsRaw.copy(apps = subsRaw.apps.filter { a -> a.id != menuAppRawVal.id })) - DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis())) - DbSet.subsConfigDao.delete(subsItemVal.id, menuAppRawVal.id) - toast("删除成功") - } - menuRawApp = null + Text(text = "复制", modifier = Modifier + .clickable { + ClipboardUtils.copyText( + json.encodeToJson5String(menuAppRawVal) + ) + toast("复制成功") + menuRawApp = null + } + .fillMaxWidth() + .padding(16.dp)) + Text(text = "删除", modifier = Modifier + .clickable { + // 也许需要二次确认 + vm.viewModelScope.launchTry(Dispatchers.IO) { + updateSubscription(subsRaw.copy(apps = subsRaw.apps.filter { a -> a.id != menuAppRawVal.id })) + DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis())) + DbSet.subsConfigDao.delete(subsItemVal.id, menuAppRawVal.id) + toast("删除成功") } - .fillMaxWidth() - .padding(16.dp), color = MaterialTheme.colorScheme.error) - } + menuRawApp = null + } + .fillMaxWidth() + .padding(16.dp), color = MaterialTheme.colorScheme.error) } } } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/home/AppListPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/home/AppListPage.kt index 277ab90870..3046b7711e 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/home/AppListPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/home/AppListPage.kt @@ -48,6 +48,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -106,7 +107,7 @@ fun useAppListPage(): ScaffoldExt { AppBarTextField( value = searchStr, onValueChange = { newValue -> vm.searchStrFlow.value = newValue.trim() }, - hint = "请输入应用名称", + hint = "请输入应用名称/ID", modifier = Modifier.focusRequester(focusRequester) ) } else { @@ -280,6 +281,16 @@ fun useAppListPage(): ScaffoldExt { } } } + item { + Spacer(modifier = Modifier.height(40.dp)) + if (orderedAppInfos.isEmpty() && searchStr.isNotEmpty()) { + Text( + text = "暂无搜索结果", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + } + } } } } \ No newline at end of file diff --git a/app/src/main/kotlin/li/songe/gkd/ui/home/SettingsPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/home/SettingsPage.kt index a1443fae71..57de6ad964 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/home/SettingsPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/home/SettingsPage.kt @@ -102,29 +102,27 @@ fun useSettingsPage(): ScaffoldExt { .padding(16.dp), shape = RoundedCornerShape(16.dp), ) { - Column { - updateTimeRadioOptions.forEach { option -> - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .selectable(selected = (option.second == store.updateSubsInterval), - onClick = { - storeFlow.value = - store.copy(updateSubsInterval = option.second) - - }) - .padding(horizontal = 16.dp) - ) { - RadioButton( - selected = (option.second == store.updateSubsInterval), + updateTimeRadioOptions.forEach { option -> + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .selectable(selected = (option.second == store.updateSubsInterval), onClick = { - storeFlow.value = store.copy(updateSubsInterval = option.second) + storeFlow.value = + store.copy(updateSubsInterval = option.second) + }) - Text( - text = option.first, modifier = Modifier.padding(start = 16.dp) - ) - } + .padding(horizontal = 16.dp) + ) { + RadioButton( + selected = (option.second == store.updateSubsInterval), + onClick = { + storeFlow.value = store.copy(updateSubsInterval = option.second) + }) + Text( + text = option.first, modifier = Modifier.padding(start = 16.dp) + ) } } } @@ -139,28 +137,26 @@ fun useSettingsPage(): ScaffoldExt { .padding(16.dp), shape = RoundedCornerShape(16.dp), ) { - Column { - darkThemeRadioOptions.forEach { option -> - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .selectable(selected = (option.second == store.enableDarkTheme), - onClick = { - storeFlow.value = - store.copy(enableDarkTheme = option.second) - }) - .padding(horizontal = 16.dp) - ) { - RadioButton( - selected = (option.second == store.enableDarkTheme), + darkThemeRadioOptions.forEach { option -> + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .selectable(selected = (option.second == store.enableDarkTheme), onClick = { - storeFlow.value = store.copy(enableDarkTheme = option.second) + storeFlow.value = + store.copy(enableDarkTheme = option.second) }) - Text( - text = option.first, modifier = Modifier.padding(start = 16.dp) - ) - } + .padding(horizontal = 16.dp) + ) { + RadioButton( + selected = (option.second == store.enableDarkTheme), + onClick = { + storeFlow.value = store.copy(enableDarkTheme = option.second) + }) + Text( + text = option.first, modifier = Modifier.padding(start = 16.dp) + ) } } } @@ -215,49 +211,47 @@ fun useSettingsPage(): ScaffoldExt { .padding(16.dp), shape = RoundedCornerShape(16.dp), ) { - Column { - val modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - Text( - text = "调用系统分享", modifier = Modifier - .clickable(onClick = { - showShareLogDlg = false - vm.viewModelScope.launchTry(Dispatchers.IO) { - val logZipFile = File(logZipDir, "log.zip") - ZipUtils.zipFiles(LogUtils.getLogFiles(), logZipFile) - val uri = FileProvider.getUriForFile( - context, "${context.packageName}.provider", logZipFile - ) - val intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_STREAM, uri) - type = "application/zip" - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - context.startActivity( - Intent.createChooser( - intent, "分享日志文件" - ) - ) - } - }) - .then(modifier) - ) - Text( - text = "生成链接(需科学上网)", modifier = Modifier - .clickable(onClick = { - showShareLogDlg = false - vm.viewModelScope.launchTry(Dispatchers.IO) { - val logZipFile = File(logZipDir, "log.zip") - ZipUtils.zipFiles(LogUtils.getLogFiles(), logZipFile) - vm.uploadZip(logZipFile) + val modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + Text( + text = "调用系统分享", modifier = Modifier + .clickable(onClick = { + showShareLogDlg = false + vm.viewModelScope.launchTry(Dispatchers.IO) { + val logZipFile = File(logZipDir, "log.zip") + ZipUtils.zipFiles(LogUtils.getLogFiles(), logZipFile) + val uri = FileProvider.getUriForFile( + context, "${context.packageName}.provider", logZipFile + ) + val intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_STREAM, uri) + type = "application/zip" + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } - }) - .then(modifier) - ) - } + context.startActivity( + Intent.createChooser( + intent, "分享日志文件" + ) + ) + } + }) + .then(modifier) + ) + Text( + text = "生成链接(需科学上网)", modifier = Modifier + .clickable(onClick = { + showShareLogDlg = false + vm.viewModelScope.launchTry(Dispatchers.IO) { + val logZipFile = File(logZipDir, "log.zip") + ZipUtils.zipFiles(LogUtils.getLogFiles(), logZipFile) + vm.uploadZip(logZipFile) + } + }) + .then(modifier) + ) } } } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/home/SubsManagePage.kt b/app/src/main/kotlin/li/songe/gkd/ui/home/SubsManagePage.kt index 2484044f34..d1959edd8b 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/home/SubsManagePage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/home/SubsManagePage.kt @@ -6,9 +6,9 @@ import android.webkit.URLUtil import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -22,6 +22,7 @@ import androidx.compose.material.icons.automirrored.filled.FormatListBulleted import androidx.compose.material.icons.filled.Add import androidx.compose.material3.AlertDialog import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -114,83 +115,81 @@ fun useSubsManagePage(): ScaffoldExt { .padding(16.dp), shape = RoundedCornerShape(16.dp), ) { - Column { - val subsRawVal = subsIdToRaw[menuSubItemVal.id] - if (subsRawVal != null) { - Text(text = "应用规则", modifier = Modifier - .clickable { - menuSubItem = null - navController.navigate(SubsPageDestination(subsRawVal.id)) - } - .fillMaxWidth() - .padding(16.dp)) - HorizontalDivider() - Text(text = "查看类别", modifier = Modifier - .clickable { - menuSubItem = null - navController.navigate(CategoryPageDestination(subsRawVal.id)) - } - .fillMaxWidth() - .padding(16.dp)) - HorizontalDivider() - Text(text = "全局规则", modifier = Modifier - .clickable { - menuSubItem = null - navController.navigate(GlobalRulePageDestination(subsRawVal.id)) - } - .fillMaxWidth() - .padding(16.dp)) - HorizontalDivider() - } - if (menuSubItemVal.id < 0 && subsRawVal != null) { - Text(text = "分享文件", modifier = Modifier - .clickable { - menuSubItem = null - vm.viewModelScope.launchTry { - val subsFile = subsFolder.resolve("${menuSubItemVal.id}.json") - context.shareFile(subsFile, "分享订阅文件") - } - } - .fillMaxWidth() - .padding(16.dp)) - HorizontalDivider() - } - if (menuSubItemVal.updateUrl != null) { - Text(text = "复制链接", modifier = Modifier - .clickable { - menuSubItem = null - ClipboardUtils.copyText(menuSubItemVal.updateUrl) - toast("复制成功") + val subsRawVal = subsIdToRaw[menuSubItemVal.id] + if (subsRawVal != null) { + Text(text = "应用规则", modifier = Modifier + .clickable { + menuSubItem = null + navController.navigate(SubsPageDestination(subsRawVal.id)) + } + .fillMaxWidth() + .padding(16.dp)) + HorizontalDivider() + Text(text = "查看类别", modifier = Modifier + .clickable { + menuSubItem = null + navController.navigate(CategoryPageDestination(subsRawVal.id)) + } + .fillMaxWidth() + .padding(16.dp)) + HorizontalDivider() + Text(text = "全局规则", modifier = Modifier + .clickable { + menuSubItem = null + navController.navigate(GlobalRulePageDestination(subsRawVal.id)) + } + .fillMaxWidth() + .padding(16.dp)) + HorizontalDivider() + } + if (menuSubItemVal.id < 0 && subsRawVal != null) { + Text(text = "分享文件", modifier = Modifier + .clickable { + menuSubItem = null + vm.viewModelScope.launchTry { + val subsFile = subsFolder.resolve("${menuSubItemVal.id}.json") + context.shareFile(subsFile, "分享订阅文件") } - .fillMaxWidth() - .padding(16.dp)) - HorizontalDivider() - } - if (subsRawVal?.supportUri != null) { - Text(text = "问题反馈", modifier = Modifier + } + .fillMaxWidth() + .padding(16.dp)) + HorizontalDivider() + } + if (menuSubItemVal.updateUrl != null) { + Text(text = "复制链接", modifier = Modifier + .clickable { + menuSubItem = null + ClipboardUtils.copyText(menuSubItemVal.updateUrl) + toast("复制成功") + } + .fillMaxWidth() + .padding(16.dp)) + HorizontalDivider() + } + if (subsRawVal?.supportUri != null) { + Text(text = "问题反馈", modifier = Modifier + .clickable { + menuSubItem = null + context.startActivity( + Intent( + Intent.ACTION_VIEW, Uri.parse(subsRawVal.supportUri) + ) + ) + } + .fillMaxWidth() + .padding(16.dp)) + HorizontalDivider() + } + if (menuSubItemVal.id != -2L) { + Text(text = "删除订阅", + modifier = Modifier .clickable { + deleteSubItem = menuSubItemVal menuSubItem = null - context.startActivity( - Intent( - Intent.ACTION_VIEW, Uri.parse(subsRawVal.supportUri) - ) - ) } .fillMaxWidth() - .padding(16.dp)) - HorizontalDivider() - } - if (menuSubItemVal.id != -2L) { - Text(text = "删除订阅", - modifier = Modifier - .clickable { - deleteSubItem = menuSubItemVal - menuSubItem = null - } - .fillMaxWidth() - .padding(16.dp), - color = MaterialTheme.colorScheme.error) - } + .padding(16.dp), + color = MaterialTheme.colorScheme.error) } } } @@ -291,35 +290,39 @@ fun useSubsManagePage(): ScaffoldExt { if (isDragging) 1.dp else 0.dp, label = "width", ) + val interactionSource = remember { MutableInteractionSource() } Card( + onClick = { menuSubItem = subItem }, modifier = Modifier - .longPressDraggableHandle(onDragStopped = { - val changeItems = mutableListOf() - orderSubItems.forEachIndexed { i, subsItem -> - if (subItems[i] != subsItem) { - changeItems.add( - subsItem.copy( - order = i + .longPressDraggableHandle( + interactionSource = interactionSource, + onDragStopped = { + val changeItems = mutableListOf() + orderSubItems.forEachIndexed { i, subsItem -> + if (subItems[i] != subsItem) { + changeItems.add( + subsItem.copy( + order = i + ) ) - ) + } } - } - if (orderSubItems.isNotEmpty()) { - vm.viewModelScope.launchTry { - DbSet.subsItemDao.update(*changeItems.toTypedArray()) + if (orderSubItems.isNotEmpty()) { + vm.viewModelScope.launchTry { + DbSet.subsItemDao.update(*changeItems.toTypedArray()) + } } - } - }) + }, + ) .animateItemPlacement() - .padding(vertical = 3.dp, horizontal = 8.dp) - .clickable { - menuSubItem = subItem - }, + .padding(vertical = 3.dp, horizontal = 8.dp), + elevation = CardDefaults.cardElevation(draggedElevation = 10.dp), shape = RoundedCornerShape(8.dp), border = if (isDragging) BorderStroke( width, MaterialTheme.colorScheme.primary - ) else null + ) else null, + interactionSource = interactionSource, ) { SubsItemCard( subsItem = subItem, diff --git a/settings.gradle.kts b/settings.gradle.kts index faedc32354..afd643ff54 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -128,9 +128,14 @@ dependencyResolutionManagement { library("androidx.room.runtime", "androidx.room:room-runtime:$roomVersion") library("androidx.room.compiler", "androidx.room:room-compiler:$roomVersion") library("androidx.room.ktx", "androidx.room:room-ktx:$roomVersion") + library("androidx.room.paging", "androidx.room:room-paging:$roomVersion") library("androidx.splashscreen", "androidx.core:core-splashscreen:1.0.1") + val pagingVersion = "3.2.1" + library("androidx.paging.runtime", "androidx.paging:paging-runtime:$pagingVersion") + library("androidx.paging.compose", "androidx.paging:paging-compose:$pagingVersion") + library( "google.accompanist.drawablepainter", "com.google.accompanist:accompanist-drawablepainter:0.34.0" @@ -203,7 +208,7 @@ dependencyResolutionManagement { library("coil.gif", "io.coil-kt:coil-gif:$coilVersion") // https://github.com/Calvin-LL/Reorderable - library("others.reorderable", "sh.calvin.reorderable:reorderable:1.3.1") + library("others.reorderable", "sh.calvin.reorderable:reorderable:1.3.2") // https://www.objecthunter.net/exp4j/ library("exp4j", "net.objecthunter:exp4j:0.4.8")