diff --git a/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt b/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt index ceb80f7..b4ceb38 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt @@ -173,7 +173,7 @@ data class RawSubscription( } val allSelectorStrings by lazy { - rules.map { r -> r.matches + (r.excludeMatches ?: emptyList()) }.flatten().distinct() + rules.map { r -> r.matches + (r.excludeMatches ?: emptyList()) }.flatten() } val allSelector by lazy { @@ -257,7 +257,7 @@ data class RawSubscription( ) : RawGroupProps, RawAppRuleProps { val allSelectorStrings by lazy { - rules.map { r -> r.matches + (r.excludeMatches ?: emptyList()) }.flatten().distinct() + rules.map { r -> r.matches + (r.excludeMatches ?: emptyList()) }.flatten() } val allSelector by lazy { diff --git a/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt b/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt index 03bdffd..5dc3503 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt @@ -16,7 +16,8 @@ sealed class ResolvedRule( ) { val key = rule.key val index = group.rules.indexOf(rule) - val preKeys = (rule.preKeys ?: emptyList()).toSet() + val othersKeys = group.rules.filter { r -> r.key != rule.key }.mapNotNull { r -> r.key }.toSet() + val preKeys = (rule.preKeys ?: emptyList()).filter { r -> othersKeys.contains(r) }.toSet() val resetMatch = rule.resetMatch ?: group.resetMatch val matches = rule.matches.map { s -> Selector.parse(s) } val excludeMatches = (rule.excludeMatches ?: emptyList()).map { s -> Selector.parse(s) } @@ -41,6 +42,16 @@ sealed class ResolvedRule( val order = rule.order ?: group.order ?: 0 + val slowSelectors by lazy { + (matches + excludeMatches).filterNot { s -> + ((quickFind && s.canQf) || s.isMatchRoot) && !s.connectKeys.contains( + "<<" + ) + } + } + + val isSlow by lazy { preKeys.isEmpty() && slowSelectors.isNotEmpty() && (matchTime == null || matchTime > 30_000L) } + var groupToRules: Map> = emptyMap() set(value) { field = value diff --git a/app/src/main/kotlin/li/songe/gkd/ui/SlowGroupPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/SlowGroupPage.kt new file mode 100644 index 0000000..25be9a3 --- /dev/null +++ b/app/src/main/kotlin/li/songe/gkd/ui/SlowGroupPage.kt @@ -0,0 +1,146 @@ +package li.songe.gkd.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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.unit.sp +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootNavGraph +import li.songe.gkd.ui.destinations.AppItemPageDestination +import li.songe.gkd.ui.destinations.GlobalRulePageDestination +import li.songe.gkd.util.LocalNavController +import li.songe.gkd.util.ProfileTransitions +import li.songe.gkd.util.appInfoCacheFlow +import li.songe.gkd.util.navigate +import li.songe.gkd.util.ruleSummaryFlow + +@RootNavGraph +@Destination(style = ProfileTransitions::class) +@Composable +fun SlowGroupPage() { + val navController = LocalNavController.current + val ruleSummary by ruleSummaryFlow.collectAsState() + val appInfoCache by appInfoCacheFlow.collectAsState() + + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + TopAppBar( + scrollBehavior = scrollBehavior, + navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = null, + ) + } + }, + title = { Text(text = if (ruleSummary.slowGroupCount > 0) "缓慢查询-${ruleSummary.slowGroupCount}" else "缓慢查询") }, + actions = {} + ) + } + ) { padding -> + LazyColumn(modifier = Modifier.padding(padding)) { + items(ruleSummary.slowGlobalGroups) { (group, rule) -> + SlowGroupCard( + modifier = Modifier + .clickable { + navController.navigate( + GlobalRulePageDestination( + rule.subsItem.id, + group.key + ) + ) + } + .padding(10.dp, 5.dp), + title = group.name, + desc = "${rule.rawSubs.name}-全局规则" + ) + } + items(ruleSummary.slowAppGroups) { (group, rule) -> + SlowGroupCard( + modifier = Modifier + .clickable { + navController.navigate( + AppItemPageDestination( + rule.subsItem.id, + rule.app.id, + group.key + ) + ) + } + .padding(10.dp, 5.dp), + title = group.name, + desc = "${rule.rawSubs.name}-应用规则-${appInfoCache[rule.app.id]?.name ?: rule.app.name ?: rule.app.id}" + ) + } + item { + Spacer(modifier = Modifier.height(40.dp)) + if (ruleSummary.slowGroupCount == 0) { + Text( + text = "暂无规则", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + } + } + } + } +} + +@Composable +fun SlowGroupCard(title: String, desc: String, modifier: Modifier = Modifier) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = modifier, + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, fontSize = 18.sp, + maxLines = 1, + softWrap = false, + overflow = TextOverflow.Ellipsis, + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = desc, fontSize = 14.sp, + maxLines = 1, + softWrap = false, + overflow = TextOverflow.Ellipsis, + ) + } + Icon( + imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = null + ) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/li/songe/gkd/ui/home/ControlPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/home/ControlPage.kt index c09ef42..0c35349 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/home/ControlPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/home/ControlPage.kt @@ -39,11 +39,13 @@ import li.songe.gkd.service.ManageService import li.songe.gkd.ui.component.AuthCard import li.songe.gkd.ui.component.TextSwitch import li.songe.gkd.ui.destinations.ClickLogPageDestination +import li.songe.gkd.ui.destinations.SlowGroupPageDestination import li.songe.gkd.util.HOME_PAGE_URL import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.checkOrRequestNotifPermission import li.songe.gkd.util.launchTry import li.songe.gkd.util.navigate +import li.songe.gkd.util.ruleSummaryFlow import li.songe.gkd.util.storeFlow import li.songe.gkd.util.updateStorage import li.songe.gkd.util.usePollState @@ -58,6 +60,7 @@ fun useControlPage(): ScaffoldExt { val latestRecordDesc by vm.latestRecordDescFlow.collectAsState() val subsStatus by vm.subsStatusFlow.collectAsState() val store by storeFlow.collectAsState() + val ruleSummary by ruleSummaryFlow.collectAsState() val gkdAccessRunning by GkdAbService.isRunning.collectAsState() val manageRunning by ManageService.isRunning.collectAsState() @@ -216,6 +219,34 @@ fun useControlPage(): ScaffoldExt { } HorizontalDivider() + if (ruleSummary.slowGroupCount > 0) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clickable { + navController.navigate(SlowGroupPageDestination) + } + .padding(10.dp, 5.dp), + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = "耗时查询", fontSize = 18.sp + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = "存在${ruleSummary.slowGroupCount}规则组,可能导致触发缓慢或更多耗电", + fontSize = 14.sp + ) + } + Icon( + imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = null + ) + } + HorizontalDivider() + } + Column( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt b/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt index 6a90db1..fbbc73a 100644 --- a/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt +++ b/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt @@ -102,6 +102,13 @@ data class RuleSummary( } else { "暂无规则" } + + val slowGlobalGroups = + globalRules.filter { r -> r.isSlow }.distinctBy { r -> r.group }.map { r -> r.group to r } + val slowAppGroups = + appIdToRules.values.flatten().filter { r -> r.isSlow }.distinctBy { r -> r.group } + .map { r -> r.group to r } + val slowGroupCount = slowGlobalGroups.size + slowAppGroups.size } val ruleSummaryFlow by lazy { diff --git a/selector/src/commonMain/kotlin/li/songe/selector/Selector.kt b/selector/src/commonMain/kotlin/li/songe/selector/Selector.kt index 872cfc2..dcaecb0 100644 --- a/selector/src/commonMain/kotlin/li/songe/selector/Selector.kt +++ b/selector/src/commonMain/kotlin/li/songe/selector/Selector.kt @@ -27,8 +27,9 @@ class Selector internal constructor(private val propertyWrapper: PropertyWrapper var c = propertyWrapper.to val keys = mutableListOf() while (c != null) { - c!!.connectSegment.connectExpression - keys.add(c!!.connectSegment.operator.key) + c?.apply { + keys.add(connectSegment.operator.key) + } c = c?.to?.to } keys.toTypedArray()