mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-16 11:42:22 +08:00
feat: app config page
This commit is contained in:
parent
99f6684ce4
commit
6d502dd8fb
|
@ -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
|
||||
|
|
|
@ -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<String, AppInfo>,
|
||||
) : 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
|
||||
|
|
|
@ -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<String>?
|
||||
}
|
||||
|
||||
@Immutable
|
||||
interface RawGroupProps : RawCommonProps {
|
||||
val name: String
|
||||
val key: Int
|
||||
|
@ -191,6 +192,10 @@ data class RawSubscription(
|
|||
val enable: Boolean?
|
||||
val scopeKeys: List<Int>?
|
||||
val rules: List<RawRuleProps>
|
||||
|
||||
val valid: Boolean
|
||||
val errorDesc: String?
|
||||
val allExampleUrls: List<String>
|
||||
}
|
||||
|
||||
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<Long>?,
|
||||
) : 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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package li.songe.gkd.data
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Stable
|
||||
@Immutable
|
||||
data class Tuple3<T0, T1, T2>(
|
||||
val t0: T0,
|
||||
val t1: T1,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
IconButton(onClick = {}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = "more",
|
||||
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))
|
||||
Switch(checked = enable, modifier = Modifier, onCheckedChange = {})
|
||||
}
|
||||
}
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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<RawSubscription.RawAppGroup?>(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 ->
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
)
|
||||
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))
|
||||
|
||||
IconButton(onClick = {
|
||||
if (editable) {
|
||||
setMenuGroupRaw(group)
|
||||
} else {
|
||||
navController.navigate(
|
||||
GlobalRuleExcludePageDestination(
|
||||
subsItemId,
|
||||
group.key
|
||||
)
|
||||
)
|
||||
}
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = null,
|
||||
} 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))
|
||||
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
)
|
||||
}
|
||||
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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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}组规则"
|
||||
}
|
||||
|
|
28
app/src/main/kotlin/li/songe/gkd/util/ResolvedGroup.kt
Normal file
28
app/src/main/kotlin/li/songe/gkd/util/ResolvedGroup.kt
Normal file
|
@ -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)
|
|
@ -64,16 +64,14 @@ fun deleteSubscription(subsId: Long) {
|
|||
}
|
||||
|
||||
fun getGroupRawEnable(
|
||||
rawGroup: RawSubscription.RawGroupProps,
|
||||
subsConfigs: List<SubsConfig>,
|
||||
group: RawSubscription.RawGroupProps,
|
||||
subsConfig: SubsConfig?,
|
||||
category: RawSubscription.RawCategory?,
|
||||
categoryConfigs: List<CategoryConfig>
|
||||
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<GlobalRule> = persistentListOf(),
|
||||
val globalGroups: ImmutableList<RawSubscription.RawGlobalGroup> = persistentListOf(),
|
||||
val globalGroups: ImmutableList<ResolvedGlobalGroup> = persistentListOf(),
|
||||
val appIdToRules: ImmutableMap<String, ImmutableList<AppRule>> = persistentMapOf(),
|
||||
val appIdToGroups: ImmutableMap<String, ImmutableList<RawSubscription.RawAppGroup>> = persistentMapOf(),
|
||||
val appIdToAllGroups: ImmutableMap<String, ImmutableList<Pair<RawSubscription.RawAppGroup, Boolean>>> = persistentMapOf(),
|
||||
val appIdToAllGroups: ImmutableMap<String, ImmutableList<ResolvedAppGroup>> = 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<String, MutableList<AppRule>>()
|
||||
val appGroups = mutableMapOf<String, List<RawSubscription.RawAppGroup>>()
|
||||
val appAllGroups = mutableMapOf<String, List<Pair<RawSubscription.RawAppGroup, Boolean>>>()
|
||||
val appRules = HashMap<String, MutableList<AppRule>>()
|
||||
val appGroups = HashMap<String, List<RawSubscription.RawAppGroup>>()
|
||||
val appAllGroups =
|
||||
HashMap<String, List<ResolvedAppGroup>>()
|
||||
val globalRules = mutableListOf<GlobalRule>()
|
||||
val globalGroups = mutableListOf<RawSubscription.RawGlobalGroup>()
|
||||
val globalGroups = mutableListOf<ResolvedGlobalGroup>()
|
||||
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<RawSubscription.RawAppGroup>()
|
||||
val appGroupConfigs = subGroupSubsConfigs.filter { c -> c.appId == appRaw.id }
|
||||
val subAppGroupToRules = mutableMapOf<RawSubscription.RawAppGroup, List<AppRule>>()
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue
Block a user