diff --git a/app/src/main/kotlin/li/songe/gkd/data/AppRule.kt b/app/src/main/kotlin/li/songe/gkd/data/AppRule.kt index fe04ff36a..a3e475edb 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/AppRule.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/AppRule.kt @@ -1,20 +1,17 @@ package li.songe.gkd.data +import li.songe.gkd.util.ResolvedAppGroup + class AppRule( rule: RawSubscription.RawAppRule, - subsItem: SubsItem, - group: RawSubscription.RawAppGroup, - rawSubs: RawSubscription, - exclude: String?, - val app: RawSubscription.RawApp, + g: ResolvedAppGroup, val appInfo: AppInfo?, ) : ResolvedRule( rule = rule, - group = group, - subsItem = subsItem, - rawSubs = rawSubs, - exclude = exclude, + g = g, ) { + val group = g.group + val app = g.app val enable = appInfo?.let { if ((rule.excludeVersionCodes ?: group.excludeVersionCodes)?.contains(appInfo.versionCode) == true diff --git a/app/src/main/kotlin/li/songe/gkd/data/GlobalRule.kt b/app/src/main/kotlin/li/songe/gkd/data/GlobalRule.kt index 870e3aa03..a02161fd5 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/GlobalRule.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/GlobalRule.kt @@ -2,6 +2,7 @@ package li.songe.gkd.data import kotlinx.collections.immutable.ImmutableMap import li.songe.gkd.service.launcherAppId +import li.songe.gkd.util.ResolvedGlobalGroup import li.songe.gkd.util.systemAppsFlow data class GlobalApp( @@ -12,19 +13,14 @@ data class GlobalApp( ) class GlobalRule( - subsItem: SubsItem, rule: RawSubscription.RawGlobalRule, - group: RawSubscription.RawGlobalGroup, - rawSubs: RawSubscription, - exclude: String?, + g: ResolvedGlobalGroup, appInfoCache: ImmutableMap, ) : ResolvedRule( rule = rule, - group = group, - subsItem = subsItem, - rawSubs = rawSubs, - exclude = exclude, + g = g, ) { + val group = g.group private val matchAnyApp = rule.matchAnyApp ?: group.matchAnyApp ?: true private val matchLauncher = rule.matchLauncher ?: group.matchLauncher ?: false private val matchSystemApp = rule.matchSystemApp ?: group.matchSystemApp ?: false 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 9c0d274af..d803da334 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt @@ -57,7 +57,7 @@ data class RawSubscription( map } - val appGroups by lazy { + private val appGroups by lazy { apps.flatMap { a -> a.groups } } @@ -184,6 +184,7 @@ data class RawSubscription( val excludeMatches: List? } + @Immutable interface RawGroupProps : RawCommonProps { val name: String val key: Int @@ -191,6 +192,10 @@ data class RawSubscription( val enable: Boolean? val scopeKeys: List? val rules: List + + val valid: Boolean + val errorDesc: String? + val allExampleUrls: List } interface RawAppRuleProps { @@ -254,11 +259,11 @@ data class RawSubscription( (apps ?: emptyList()).associate { a -> a.id to (a.enable ?: true) } } - val errorDesc by lazy { getErrorDesc() } + override val errorDesc by lazy { getErrorDesc() } - val valid by lazy { errorDesc == null } + override val valid by lazy { errorDesc == null } - val allExampleUrls by lazy { + override val allExampleUrls by lazy { ((exampleUrls ?: emptyList()) + rules.flatMap { r -> r.exampleUrls ?: emptyList() }).distinct() @@ -322,11 +327,11 @@ data class RawSubscription( override val excludeVersionCodes: List?, ) : RawGroupProps, RawAppRuleProps { - val errorDesc by lazy { getErrorDesc() } + override val errorDesc by lazy { getErrorDesc() } - val valid by lazy { errorDesc == null } + override val valid by lazy { errorDesc == null } - val allExampleUrls by lazy { + override val allExampleUrls by lazy { ((exampleUrls ?: emptyList()) + rules.flatMap { r -> r.exampleUrls ?: emptyList() }).distinct() 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 e00e9be78..9a982f7b9 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt @@ -8,15 +8,17 @@ import li.songe.gkd.service.createCacheTransform import li.songe.gkd.service.lastTriggerRule import li.songe.gkd.service.lastTriggerTime import li.songe.gkd.service.querySelector +import li.songe.gkd.util.ResolvedGroup import li.songe.selector.Selector sealed class ResolvedRule( val rule: RawSubscription.RawRuleProps, - val group: RawSubscription.RawGroupProps, - val rawSubs: RawSubscription, - val subsItem: SubsItem, - val exclude: String?, + val g: ResolvedGroup, ) { + private val group = g.group + val subsItem = g.subsItem + val rawSubs = g.subscription + val config = g.config val key = rule.key val index = group.rules.indexOf(rule) private val preKeys = (rule.preKeys ?: emptyList()).toSet() @@ -198,7 +200,7 @@ sealed class ResolvedRule( return "id:${subsItem.id}, v:${rawSubs.version}, type:${type}, gKey=${group.key}, gName:${group.name}, index:${index}, key:${key}, status:${status.name}" } - val excludeData = ExcludeData.parse(exclude) + val excludeData = ExcludeData.parse(config?.exclude) abstract val type: String diff --git a/app/src/main/kotlin/li/songe/gkd/data/Tuple.kt b/app/src/main/kotlin/li/songe/gkd/data/Tuple.kt index 2479a91d9..81e32887c 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/Tuple.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/Tuple.kt @@ -1,8 +1,8 @@ package li.songe.gkd.data -import androidx.compose.runtime.Stable +import androidx.compose.runtime.Immutable -@Stable +@Immutable data class Tuple3( val t0: T0, val t1: T1, diff --git a/app/src/main/kotlin/li/songe/gkd/service/AbState.kt b/app/src/main/kotlin/li/songe/gkd/service/AbState.kt index 4a1d1ceb5..c87430324 100644 --- a/app/src/main/kotlin/li/songe/gkd/service/AbState.kt +++ b/app/src/main/kotlin/li/songe/gkd/service/AbState.kt @@ -122,7 +122,7 @@ fun insertClickLog(rule: ResolvedRule) { activityId = topActivityFlow.value.activityId, subsId = rule.subsItem.id, subsVersion = rule.rawSubs.version, - groupKey = rule.group.key, + groupKey = rule.g.group.key, groupType = when (rule) { is AppRule -> SubsConfig.AppGroupType is GlobalRule -> SubsConfig.GlobalGroupType diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AppConfigPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/AppConfigPage.kt index da027fc96..4990962bf 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AppConfigPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AppConfigPage.kt @@ -13,7 +13,6 @@ 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.filled.MoreVert import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -21,21 +20,33 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch 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.runtime.remember 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 androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.viewModelScope import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootNavGraph +import li.songe.gkd.data.ExcludeData +import li.songe.gkd.data.RawSubscription +import li.songe.gkd.data.SubsConfig +import li.songe.gkd.data.stringify +import li.songe.gkd.db.DbSet import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions import li.songe.gkd.util.appInfoCacheFlow +import li.songe.gkd.util.launchTry import li.songe.gkd.util.ruleSummaryFlow +import li.songe.gkd.util.toast @RootNavGraph @Destination(style = ProfileTransitions::class) @@ -46,13 +57,11 @@ fun AppConfigPage(appId: String) { val appInfoCache by appInfoCacheFlow.collectAsState() val appInfo = appInfoCache[appId] val ruleSummary by ruleSummaryFlow.collectAsState() - val globalGroups = ruleSummary.globalGroups - val appGroups = ruleSummary.appIdToAllGroups[appId] ?: emptyList() - - Scaffold(topBar = { - TopAppBar(navigationIcon = { + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { + TopAppBar(scrollBehavior = scrollBehavior, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { @@ -69,61 +78,112 @@ fun AppConfigPage(appId: String) { overflow = TextOverflow.Ellipsis, ) }, actions = {}) - }, content = { contentPadding -> + }) { contentPadding -> LazyColumn( modifier = Modifier.padding(contentPadding) ) { - items(appGroups) { (group, enable) -> - Row( - modifier = Modifier - .padding(10.dp, 6.dp) - .fillMaxWidth() - .height(45.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Column( - modifier = Modifier - .weight(1f) - .fillMaxHeight(), - verticalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = group.name, - maxLines = 1, - softWrap = false, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth() - ) - if (group.valid) { - Text( - text = group.desc ?: "", - maxLines = 1, - softWrap = false, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth(), - fontSize = 14.sp - ) - } else { - Text( - text = "非法选择器", - modifier = Modifier.fillMaxWidth(), - fontSize = 14.sp, - color = MaterialTheme.colorScheme.error + items(globalGroups) { g -> + val excludeData = remember(g.config?.exclude) { + ExcludeData.parse(g.config?.exclude) + } + val checked = getChecked(excludeData, g.group, appId, appInfo) + AppGroupCard(g.group, checked ?: false) { newChecked -> + if (checked == null) { + toast("内置禁用,不可修改") + return@AppGroupCard + } + vm.viewModelScope.launchTry { + DbSet.subsConfigDao.insert( + (g.config ?: SubsConfig( + type = SubsConfig.GlobalGroupType, + subsItemId = g.subsItem.id, + groupKey = g.group.key, + )).copy( + exclude = excludeData.copy( + appIds = excludeData.appIds.toMutableMap().apply { + set(appId, !newChecked) + } + ).stringify() ) - } + ) } - Spacer(modifier = Modifier.width(10.dp)) - IconButton(onClick = {}) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = "more", + } + } + items(appGroups) { g -> + AppGroupCard(g.group, g.enable) { + vm.viewModelScope.launchTry { + DbSet.subsConfigDao.insert( + g.config?.copy(enable = it) ?: SubsConfig( + type = SubsConfig.AppGroupType, + subsItemId = g.subsItem.id, + appId = appId, + groupKey = g.group.key, + enable = it + ) ) } - Spacer(modifier = Modifier.width(10.dp)) - Switch(checked = enable, modifier = Modifier, onCheckedChange = {}) } } + item { + Spacer(modifier = Modifier.height(40.dp)) + if (globalGroups.size + appGroups.size == 0) { + Text( + text = "暂无规则", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + } + } + } + } +} + +@Composable +private fun AppGroupCard( + group: RawSubscription.RawGroupProps, + enable: Boolean, + onCheckedChange: ((Boolean) -> Unit)?, +) { + Row( + modifier = Modifier + .padding(10.dp, 6.dp) + .fillMaxWidth() + .height(45.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + modifier = Modifier + .weight(1f) + .fillMaxHeight(), + verticalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = group.name, + maxLines = 1, + softWrap = false, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth() + ) + if (group.valid) { + Text( + text = group.desc ?: "", + maxLines = 1, + softWrap = false, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth(), + fontSize = 14.sp + ) + } else { + Text( + text = "非法选择器", + modifier = Modifier.fillMaxWidth(), + fontSize = 14.sp, + color = MaterialTheme.colorScheme.error + ) + } } - }) + Spacer(modifier = Modifier.width(10.dp)) + Switch(checked = enable, modifier = Modifier, onCheckedChange = onCheckedChange) + } } \ No newline at end of file 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 c3f184f25..feb48d817 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt @@ -32,6 +32,7 @@ import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextButton 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 @@ -42,6 +43,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -113,8 +115,9 @@ fun AppItemPage( mutableStateOf(null) } - Scaffold(topBar = { - TopAppBar(navigationIcon = { + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { + TopAppBar(scrollBehavior = scrollBehavior, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { @@ -145,7 +148,7 @@ fun AppItemPage( ) } } - }, content = { contentPadding -> + }) { contentPadding -> LazyColumn( modifier = Modifier .fillMaxSize() @@ -213,9 +216,9 @@ fun AppItemPage( val groupEnable = getGroupRawEnable( group, - subsConfigs, + subsConfigs.find { c -> c.groupKey == group.key }, groupToCategoryMap[group], - categoryConfigs + categoryConfigs.find { c -> c.categoryKey == groupToCategoryMap[group]?.key } ) val subsConfig = subsConfigs.find { it.groupKey == group.key } Switch( @@ -238,7 +241,7 @@ fun AppItemPage( Spacer(modifier = Modifier.height(20.dp)) } } - }) + } showGroupItem?.let { showGroupItemVal -> 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 cfb7bc9a0..d384456a8 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/CategoryPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/CategoryPage.kt @@ -29,6 +29,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton 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 @@ -37,6 +38,7 @@ import androidx.compose.runtime.remember 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.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -83,8 +85,9 @@ fun CategoryPage(subsItemId: Long) { val categories = subsRaw?.categories ?: emptyList() val categoriesGroups = subsRaw?.categoryToGroupsMap ?: emptyMap() - Scaffold(topBar = { - TopAppBar(navigationIcon = { + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { + TopAppBar(scrollBehavior = scrollBehavior, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { @@ -103,7 +106,7 @@ fun CategoryPage(subsItemId: Long) { ) } } - }, content = { contentPadding -> + }) { contentPadding -> LazyColumn( modifier = Modifier.padding(contentPadding) ) { @@ -167,7 +170,7 @@ fun CategoryPage(subsItemId: Long) { } } } - }) + } editEnableCategory?.let { category -> val categoryConfig = 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 02dcf9775..3ee92bf00 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRulePage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRulePage.kt @@ -31,6 +31,7 @@ import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextButton 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 @@ -40,6 +41,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +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 @@ -93,10 +95,11 @@ fun GlobalRulePage(subsItemId: Long, focusGroupKey: Int? = null) { null ) } - + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - TopAppBar(navigationIcon = { + TopAppBar(scrollBehavior = scrollBehavior, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { @@ -108,7 +111,8 @@ fun GlobalRulePage(subsItemId: Long, focusGroupKey: Int? = null) { }, title = { Text(text = "${rawSubs?.name ?: subsItemId}/全局规则") }) - }, floatingActionButton = { + }, + floatingActionButton = { if (editable) { FloatingActionButton(onClick = { showAddDlg = true }) { Icon( @@ -118,103 +122,102 @@ fun GlobalRulePage(subsItemId: Long, focusGroupKey: Int? = null) { } } }, - content = { paddingValues -> - LazyColumn(modifier = Modifier.padding(paddingValues)) { - items(globalGroups, { g -> g.key }) { group -> - Row( + ) { paddingValues -> + LazyColumn(modifier = Modifier.padding(paddingValues)) { + items(globalGroups, { g -> g.key }) { group -> + Row( + modifier = Modifier + .background( + if (group.key == focusGroupKey) MaterialTheme.colorScheme.inversePrimary else Color.Transparent + ) + .clickable { setShowGroupItem(group) } + .padding(10.dp, 6.dp) + .fillMaxWidth() + .height(45.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( modifier = Modifier - .background( - if (group.key == focusGroupKey) MaterialTheme.colorScheme.inversePrimary else Color.Transparent - ) - .clickable { setShowGroupItem(group) } - .padding(10.dp, 6.dp) - .fillMaxWidth() - .height(45.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, + .weight(1f) + .fillMaxHeight(), + verticalArrangement = Arrangement.SpaceBetween ) { - Column( - modifier = Modifier - .weight(1f) - .fillMaxHeight(), - verticalArrangement = Arrangement.SpaceBetween - ) { + Text( + text = group.name, + maxLines = 1, + softWrap = false, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth() + ) + if (group.valid) { Text( - text = group.name, + text = group.desc ?: "", maxLines = 1, softWrap = false, overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + fontSize = 14.sp + ) + } else { + Text( + text = "非法选择器", + modifier = Modifier.fillMaxWidth(), + fontSize = 14.sp, + color = MaterialTheme.colorScheme.error ) - if (group.valid) { - Text( - text = group.desc ?: "", - maxLines = 1, - softWrap = false, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth(), - fontSize = 14.sp - ) - } else { - Text( - text = "非法选择器", - modifier = Modifier.fillMaxWidth(), - fontSize = 14.sp, - color = MaterialTheme.colorScheme.error - ) - } } - Spacer(modifier = Modifier.width(10.dp)) + } + Spacer(modifier = Modifier.width(10.dp)) - IconButton(onClick = { - if (editable) { - setMenuGroupRaw(group) - } else { - navController.navigate( - GlobalRuleExcludePageDestination( - subsItemId, - group.key - ) + IconButton(onClick = { + if (editable) { + setMenuGroupRaw(group) + } else { + navController.navigate( + GlobalRuleExcludePageDestination( + subsItemId, + group.key ) - } - }) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = null, ) } - Spacer(modifier = Modifier.width(10.dp)) - - val groupEnable = subsConfigs.find { c -> c.groupKey == group.key }?.enable - ?: group.enable ?: true - val subsConfig = subsConfigs.find { it.groupKey == group.key } - Switch( - checked = groupEnable, modifier = Modifier, - onCheckedChange = vm.viewModelScope.launchAsFn { enable -> - val newItem = (subsConfig?.copy(enable = enable) ?: SubsConfig( - type = SubsConfig.GlobalGroupType, - subsItemId = subsItemId, - groupKey = group.key, - enable = enable - )) - DbSet.subsConfigDao.insert(newItem) - } + }) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = null, ) } + Spacer(modifier = Modifier.width(10.dp)) + + val groupEnable = subsConfigs.find { c -> c.groupKey == group.key }?.enable + ?: group.enable ?: true + val subsConfig = subsConfigs.find { it.groupKey == group.key } + Switch( + checked = groupEnable, modifier = Modifier, + onCheckedChange = vm.viewModelScope.launchAsFn { enable -> + val newItem = (subsConfig?.copy(enable = enable) ?: SubsConfig( + type = SubsConfig.GlobalGroupType, + subsItemId = subsItemId, + groupKey = group.key, + enable = enable + )) + DbSet.subsConfigDao.insert(newItem) + } + ) } - item { - Spacer(modifier = Modifier.height(40.dp)) - if (globalGroups.isEmpty()) { - Text( - text = "暂无规则", - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) - } + } + item { + Spacer(modifier = Modifier.height(40.dp)) + if (globalGroups.isEmpty()) { + Text( + text = "暂无规则", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) } } } - ) + } if (showAddDlg && rawSubs != null) { var source by remember { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/SubsVm.kt b/app/src/main/kotlin/li/songe/gkd/ui/SubsVm.kt index 2bb36b884..e96b64fc2 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SubsVm.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SubsVm.kt @@ -101,7 +101,12 @@ class SubsVm @Inject constructor(stateHandle: SavedStateHandle) : ViewModel() { apps.map { app -> val appGroupSubsConfigs = groupSubsConfigs.filter { s -> s.appId == app.id } val enableSize = app.groups.count { g -> - getGroupRawEnable(g, appGroupSubsConfigs, groupToCategoryMap[g], categoryConfigs) + getGroupRawEnable( + g, + appGroupSubsConfigs.find { c -> c.groupKey == g.key }, + groupToCategoryMap[g], + categoryConfigs.find { c -> c.categoryKey == groupToCategoryMap[g]?.key } + ) } Tuple3(app, appSubsConfigs.find { s -> s.appId == app.id }, enableSize) } 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 3046b7711..44b1d46be 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 @@ -244,7 +244,7 @@ fun useAppListPage(): ScaffoldExt { val appGroups = ruleSummary.appIdToAllGroups[appInfo.id] ?: emptyList() val appDesc = if (appGroups.isNotEmpty()) { - when (val disabledCount = appGroups.count { g -> !g.second }) { + when (val disabledCount = appGroups.count { g -> !g.enable }) { 0 -> { "${appGroups.size}组规则" } diff --git a/app/src/main/kotlin/li/songe/gkd/util/ResolvedGroup.kt b/app/src/main/kotlin/li/songe/gkd/util/ResolvedGroup.kt new file mode 100644 index 000000000..a623b2424 --- /dev/null +++ b/app/src/main/kotlin/li/songe/gkd/util/ResolvedGroup.kt @@ -0,0 +1,28 @@ +package li.songe.gkd.util + +import li.songe.gkd.data.RawSubscription +import li.songe.gkd.data.SubsConfig +import li.songe.gkd.data.SubsItem + +sealed class ResolvedGroup( + open val group: RawSubscription.RawGroupProps, + val subscription: RawSubscription, + val subsItem: SubsItem, + val config: SubsConfig?, +) + +class ResolvedAppGroup( + override val group: RawSubscription.RawAppGroup, + subscription: RawSubscription, + subsItem: SubsItem, + config: SubsConfig?, + val app: RawSubscription.RawApp, + val enable: Boolean, +) : ResolvedGroup(group, subscription, subsItem, config) + +class ResolvedGlobalGroup( + override val group: RawSubscription.RawGlobalGroup, + subscription: RawSubscription, + subsItem: SubsItem, + config: SubsConfig?, +) : ResolvedGroup(group, subscription, subsItem, config) \ No newline at end of file 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 4e4bcfb29..6a0c447f0 100644 --- a/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt +++ b/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt @@ -64,16 +64,14 @@ fun deleteSubscription(subsId: Long) { } fun getGroupRawEnable( - rawGroup: RawSubscription.RawGroupProps, - subsConfigs: List, + group: RawSubscription.RawGroupProps, + subsConfig: SubsConfig?, category: RawSubscription.RawCategory?, - categoryConfigs: List + categoryConfig: CategoryConfig?, ): Boolean { // 优先级: 规则用户配置 > 批量配置 > 批量默认 > 规则默认 - val groupConfig = subsConfigs.find { c -> c.groupKey == rawGroup.key } // 1.规则用户配置 - return groupConfig?.enable ?: if (category != null) {// 这个规则被批量配置捕获 - val categoryConfig = categoryConfigs.find { c -> c.categoryKey == category.key } + return subsConfig?.enable ?: if (category != null) {// 这个规则被批量配置捕获 val enable = if (categoryConfig != null) { // 2.批量配置 categoryConfig.enable @@ -84,15 +82,15 @@ fun getGroupRawEnable( enable } else { null - } ?: rawGroup.enable ?: true + } ?: group.enable ?: true } data class RuleSummary( val globalRules: ImmutableList = persistentListOf(), - val globalGroups: ImmutableList = persistentListOf(), + val globalGroups: ImmutableList = persistentListOf(), val appIdToRules: ImmutableMap> = persistentMapOf(), val appIdToGroups: ImmutableMap> = persistentMapOf(), - val appIdToAllGroups: ImmutableMap>> = persistentMapOf(), + val appIdToAllGroups: ImmutableMap> = persistentMapOf(), ) { private val appSize = appIdToRules.keys.size private val appGroupSize = appIdToGroups.values.sumOf { s -> s.size } @@ -116,7 +114,8 @@ data class RuleSummary( } val slowGlobalGroups = - globalRules.filter { r -> r.isSlow }.distinctBy { r -> r.group }.map { r -> r.group to r } + 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 } @@ -134,11 +133,12 @@ val ruleSummaryFlow by lazy { val globalSubsConfigs = subsConfigs.filter { c -> c.type == SubsConfig.GlobalGroupType } val appSubsConfigs = subsConfigs.filter { c -> c.type == SubsConfig.AppType } val groupSubsConfigs = subsConfigs.filter { c -> c.type == SubsConfig.AppGroupType } - val appRules = mutableMapOf>() - val appGroups = mutableMapOf>() - val appAllGroups = mutableMapOf>>() + val appRules = HashMap>() + val appGroups = HashMap>() + val appAllGroups = + HashMap>() val globalRules = mutableListOf() - val globalGroups = mutableListOf() + val globalGroups = mutableListOf() subsItems.filter { it.enable }.forEach { subsItem -> val rawSubs = subsIdToRaw[subsItem.id] ?: return@forEach @@ -150,14 +150,18 @@ val ruleSummaryFlow by lazy { g.valid && (subGlobalSubsConfigs.find { c -> c.groupKey == g.key }?.enable ?: g.enable ?: true) }.forEach { groupRaw -> - globalGroups.add(groupRaw) + val config = subGlobalSubsConfigs.find { c -> c.groupKey == groupRaw.key } + val g = ResolvedGlobalGroup( + group = groupRaw, + subscription = rawSubs, + subsItem = subsItem, + config = config + ) + globalGroups.add(g) val subRules = groupRaw.rules.map { ruleRaw -> GlobalRule( rule = ruleRaw, - group = groupRaw, - rawSubs = rawSubs, - subsItem = subsItem, - exclude = subGlobalSubsConfigs.find { c -> c.groupKey == groupRaw.key }?.exclude, + g = g, appInfoCache = appInfoCache, ) } @@ -183,31 +187,34 @@ val ruleSummaryFlow by lazy { val subAppGroups = mutableListOf() val appGroupConfigs = subGroupSubsConfigs.filter { c -> c.appId == appRaw.id } val subAppGroupToRules = mutableMapOf>() - val groupAndEnables = appRaw.groups.map { groupRaw -> - val enable = groupRaw.valid && getGroupRawEnable( - groupRaw, - appGroupConfigs, - rawSubs.groupToCategoryMap[groupRaw], - subCategoryConfigs + val groupAndEnables = appRaw.groups.map { group -> + val enable = group.valid && getGroupRawEnable( + group, + appGroupConfigs.find { c -> c.groupKey == group.key }, + rawSubs.groupToCategoryMap[group], + subCategoryConfigs.find { c -> c.categoryKey == rawSubs.groupToCategoryMap[group]?.key } + ) + ResolvedAppGroup( + group = group, + subscription = rawSubs, + subsItem = subsItem, + config = appGroupConfigs.find { c -> c.groupKey == group.key }, + app = appRaw, + enable = enable ) - groupRaw to enable } appAllGroups[appRaw.id] = (appAllGroups[appRaw.id] ?: emptyList()) + groupAndEnables - groupAndEnables.forEach { (groupRaw, enable) -> - if (enable) { - subAppGroups.add(groupRaw) - val subRules = groupRaw.rules.map { ruleRaw -> + groupAndEnables.forEach { g -> + if (g.enable) { + subAppGroups.add(g.group) + val subRules = g.group.rules.map { ruleRaw -> AppRule( rule = ruleRaw, - group = groupRaw, - app = appRaw, - rawSubs = rawSubs, - subsItem = subsItem, - exclude = appGroupConfigs.find { c -> c.groupKey == groupRaw.key }?.exclude, + g = g, appInfo = appInfoCache[appRaw.id] ) }.filter { r -> r.enable } - subAppGroupToRules[groupRaw] = subRules + subAppGroupToRules[g.group] = subRules if (subRules.isNotEmpty()) { val rules = appRules[appRaw.id] ?: mutableListOf() appRules[appRaw.id] = rules