From 8892dd3da8630dce60e9a8a92df576be4869336d Mon Sep 17 00:00:00 2001 From: lisonge Date: Thu, 3 Oct 2024 01:24:02 +0800 Subject: [PATCH] perf: save filter settings --- .../kotlin/li/songe/gkd/ui/AppConfigPage.kt | 43 +++++++++++-------- .../kotlin/li/songe/gkd/ui/AppConfigVm.kt | 9 ++-- .../kotlin/li/songe/gkd/ui/AuthA11yPage.kt | 2 +- .../li/songe/gkd/ui/GlobalRuleExcludePage.kt | 35 ++++++++------- .../li/songe/gkd/ui/GlobalRuleExcludeVm.kt | 12 ++++-- .../main/kotlin/li/songe/gkd/ui/SubsPage.kt | 36 +++++++++------- app/src/main/kotlin/li/songe/gkd/ui/SubsVm.kt | 8 ++-- .../li/songe/gkd/ui/home/AppListPage.kt | 17 +++++--- .../main/kotlin/li/songe/gkd/util/Others.kt | 5 +++ .../main/kotlin/li/songe/gkd/util/Store.kt | 12 ++++-- 10 files changed, 110 insertions(+), 69 deletions(-) create mode 100644 app/src/main/kotlin/li/songe/gkd/util/Others.kt 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 0a2cfda..81f6a23 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AppConfigPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AppConfigPage.kt @@ -74,6 +74,7 @@ import li.songe.gkd.util.ResolvedGroup import li.songe.gkd.util.RuleSortOption import li.songe.gkd.util.appInfoCacheFlow import li.songe.gkd.util.launchTry +import li.songe.gkd.util.storeFlow import li.songe.gkd.util.throttle @Destination(style = ProfileTransitions::class) @@ -148,12 +149,12 @@ fun AppConfigPage(appId: String) { RadioButton( selected = ruleSortType == s, onClick = { - vm.ruleSortTypeFlow.update { s } + storeFlow.update { it.copy(appRuleSortType = s.value) } } ) }, onClick = { - vm.ruleSortTypeFlow.update { s } + storeFlow.update { it.copy(appRuleSortType = s.value) } }, ) } @@ -164,7 +165,8 @@ fun AppConfigPage(appId: String) { floatingActionButton = { FloatingActionButton( onClick = throttle { - navController.toDestinationsNavigator().navigate(AppItemPageDestination(LOCAL_SUBS_ID, appId)) + navController.toDestinationsNavigator() + .navigate(AppItemPageDestination(LOCAL_SUBS_ID, appId)) }, content = { Icon( @@ -295,7 +297,6 @@ private fun AppGroupCard( onClick: () -> Unit, onCheckedChange: ((Boolean) -> Unit)?, ) { - val context = LocalContext.current as MainActivity Row( modifier = Modifier .clickable(onClick = onClick) @@ -349,20 +350,26 @@ private fun AppGroupCard( if (checked != null) { Switch(checked = checked, onCheckedChange = onCheckedChange) } else { - Switch( - checked = false, - enabled = false, - onCheckedChange = null, - modifier = Modifier.clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - ) { - context.mainVm.dialogFlow.updateDialogOptions( - title = "内置禁用", - text = "此规则组已经在其 apps 字段中配置对当前应用的禁用, 因此无法手动开启规则组\n\n提示: 这种情况一般在此全局规则无法适配/跳过适配/单独适配当前应用时出现", - ) - } - ) + InnerDisableSwitch() } } +} + +@Composable +fun InnerDisableSwitch() { + val context = LocalContext.current as MainActivity + Switch( + checked = false, + enabled = false, + onCheckedChange = null, + modifier = Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + ) { + context.mainVm.dialogFlow.updateDialogOptions( + title = "内置禁用", + text = "此规则组已经在其 apps 字段中配置对当前应用的禁用, 因此无法手动开启规则组\n\n提示: 这种情况一般在此全局规则无法适配/跳过适配/单独适配当前应用时出现", + ) + } + ) } \ No newline at end of file diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AppConfigVm.kt b/app/src/main/kotlin/li/songe/gkd/ui/AppConfigVm.kt index d32d1d6..7b9c13c 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AppConfigVm.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AppConfigVm.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.ramcosta.composedestinations.generated.destinations.AppConfigPageDestination -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest @@ -16,11 +15,14 @@ import li.songe.gkd.util.ResolvedAppGroup import li.songe.gkd.util.ResolvedGlobalGroup import li.songe.gkd.util.RuleSortOption import li.songe.gkd.util.collator +import li.songe.gkd.util.findOption import li.songe.gkd.util.getGroupRawEnable +import li.songe.gkd.util.map +import li.songe.gkd.util.storeFlow import li.songe.gkd.util.subsIdToRawFlow import li.songe.gkd.util.subsItemsFlow -class AppConfigVm (stateHandle: SavedStateHandle) : ViewModel() { +class AppConfigVm(stateHandle: SavedStateHandle) : ViewModel() { private val args = AppConfigPageDestination.argsFrom(stateHandle) private val latestGlobalLogsFlow = DbSet.clickLogDao.queryAppLatest( @@ -33,7 +35,8 @@ class AppConfigVm (stateHandle: SavedStateHandle) : ViewModel() { SubsConfig.AppGroupType ) - val ruleSortTypeFlow = MutableStateFlow(RuleSortOption.Default) + val ruleSortTypeFlow = + storeFlow.map(viewModelScope) { RuleSortOption.allSubObject.findOption(it.appRuleSortType) } private val subsFlow = combine(subsIdToRawFlow, subsItemsFlow) { subsIdToRaw, subsItems -> subsItems.mapNotNull { if (it.enable && subsIdToRaw[it.id] != null) it to subsIdToRaw[it.id]!! else null } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yPage.kt index 82502b7..f858940 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yPage.kt @@ -259,7 +259,7 @@ fun AuthA11yPage() { } } -private val commandText by lazy { "adb pm grant ${META.appId} android.permission.WRITE_SECURE_SETTINGS" } +private val commandText by lazy { "adb shell pm grant ${META.appId} android.permission.WRITE_SECURE_SETTINGS" } private suspend fun MainActivity.grantPermissionByShizuku() { if (shizukuOkState.stateFlow.value) { 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 87b74fb..3bf2588 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludePage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludePage.kt @@ -61,6 +61,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph +import kotlinx.coroutines.flow.update import li.songe.gkd.data.AppInfo import li.songe.gkd.data.ExcludeData import li.songe.gkd.data.RawSubscription @@ -79,6 +80,8 @@ import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions import li.songe.gkd.util.SortTypeOption import li.songe.gkd.util.launchTry +import li.songe.gkd.util.mapHashCode +import li.songe.gkd.util.storeFlow import li.songe.gkd.util.toast @Destination(style = ProfileTransitions::class) @@ -109,9 +112,16 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) { }) val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val listState = rememberLazyListState() - LaunchedEffect(key1 = showAppInfos, block = { - listState.animateScrollToItem(0) - }) + var isFirstVisit by remember { mutableStateOf(true) } + LaunchedEffect( + key1 = showAppInfos.mapHashCode { it.id }, + ) { + if (isFirstVisit) { + isFirstVisit = false + } else { + listState.scrollToItem(0) + } + } var expanded by remember { mutableStateOf(false) } Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { TopAppBar(scrollBehavior = scrollBehavior, navigationIcon = { @@ -190,12 +200,12 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) { trailingIcon = { RadioButton(selected = sortType == sortOption, onClick = { - vm.sortTypeFlow.value = sortOption + storeFlow.update { it.copy(subsExcludeSortType = sortOption.value) } } ) }, onClick = { - vm.sortTypeFlow.value = sortOption + storeFlow.update { it.copy(subsExcludeSortType = sortOption.value) } }, ) } @@ -213,11 +223,11 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) { Checkbox( checked = showSystemApp, onCheckedChange = { - vm.showSystemAppFlow.value = !vm.showSystemAppFlow.value + storeFlow.update { it.copy(subsExcludeShowSystemApp = !it.subsExcludeShowSystemApp) } }) }, onClick = { - vm.showSystemAppFlow.value = !vm.showSystemAppFlow.value + storeFlow.update { it.copy(subsExcludeShowSystemApp = !it.subsExcludeShowSystemApp) } }, ) DropdownMenuItem( @@ -228,12 +238,11 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) { Checkbox( checked = showHiddenApp, onCheckedChange = { - vm.showHiddenAppFlow.value = - !vm.showHiddenAppFlow.value + storeFlow.update { it.copy(subsExcludeShowHiddenApp = !it.subsExcludeShowHiddenApp) } }) }, onClick = { - vm.showHiddenAppFlow.value = !vm.showHiddenAppFlow.value + storeFlow.update { it.copy(subsExcludeShowHiddenApp = !it.subsExcludeShowHiddenApp) } }, ) } @@ -334,11 +343,7 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) { }, ) } else { - Switch( - enabled = false, - checked = false, - onCheckedChange = {}, - ) + InnerDisableSwitch() } } } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludeVm.kt b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludeVm.kt index e8f4241..1e129a3 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludeVm.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludeVm.kt @@ -13,11 +13,13 @@ import kotlinx.coroutines.flow.stateIn import li.songe.gkd.data.ExcludeData import li.songe.gkd.db.DbSet import li.songe.gkd.util.SortTypeOption +import li.songe.gkd.util.findOption import li.songe.gkd.util.map import li.songe.gkd.util.orderedAppInfosFlow +import li.songe.gkd.util.storeFlow import li.songe.gkd.util.subsIdToRawFlow -class GlobalRuleExcludeVm (stateHandle: SavedStateHandle) : ViewModel() { +class GlobalRuleExcludeVm(stateHandle: SavedStateHandle) : ViewModel() { private val args = GlobalRuleExcludePageDestination.argsFrom(stateHandle) val rawSubsFlow = subsIdToRawFlow.map(viewModelScope) { it[args.subsItemId] } @@ -40,9 +42,11 @@ class GlobalRuleExcludeVm (stateHandle: SavedStateHandle) : ViewModel() { DbSet.clickLogDao.queryLatestUniqueAppIds(args.subsItemId, args.groupKey).map { appIds -> appIds.mapIndexed { index, appId -> appId to index }.toMap() } - val sortTypeFlow = MutableStateFlow(SortTypeOption.SortByName) - val showSystemAppFlow = MutableStateFlow(true) - val showHiddenAppFlow = MutableStateFlow(false) + val sortTypeFlow = storeFlow.map(viewModelScope) { + SortTypeOption.allSubObject.findOption(it.subsExcludeSortType) + } + val showSystemAppFlow = storeFlow.map(viewModelScope) { it.subsExcludeShowSystemApp } + val showHiddenAppFlow = storeFlow.map(viewModelScope) { it.subsExcludeShowHiddenApp } val showAppInfosFlow = combine(orderedAppInfosFlow.combine(showHiddenAppFlow) { appInfos, showHiddenApp -> if (showHiddenApp) { 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 c4f8403..c1cc260 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt @@ -51,6 +51,7 @@ import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.AppItemPageDestination import com.ramcosta.composedestinations.utils.toDestinationsNavigator +import kotlinx.coroutines.flow.update import li.songe.gkd.MainActivity import li.songe.gkd.data.RawSubscription import li.songe.gkd.data.SubsConfig @@ -70,6 +71,8 @@ import li.songe.gkd.util.appInfoCacheFlow import li.songe.gkd.util.json import li.songe.gkd.util.launchAsFn import li.songe.gkd.util.launchTry +import li.songe.gkd.util.mapHashCode +import li.songe.gkd.util.storeFlow import li.songe.gkd.util.throttle import li.songe.gkd.util.toast import li.songe.gkd.util.updateSubscription @@ -117,16 +120,14 @@ fun SubsPage( val showUninstallApp by vm.showUninstallAppFlow.collectAsState() val sortType by vm.sortTypeFlow.collectAsState() val listState = rememberLazyListState() - var isFirstVisit by remember { mutableStateOf(false) } + var isFirstVisit by remember { mutableStateOf(true) } LaunchedEffect( - appAndConfigs.size, - sortType.value, - appAndConfigs.fold(0) { acc, t -> 31 * acc + t.t0.id.hashCode() } + key1 = appAndConfigs.mapHashCode { it.t0.id } ) { if (isFirstVisit) { - listState.scrollToItem(0) + isFirstVisit = false } else { - isFirstVisit = true + listState.scrollToItem(0) } } @@ -197,11 +198,11 @@ fun SubsPage( RadioButton( selected = sortType == sortOption, onClick = { - vm.sortTypeFlow.value = sortOption + storeFlow.update { s -> s.copy(subsAppSortType = sortOption.value) } }) }, onClick = { - vm.sortTypeFlow.value = sortOption + storeFlow.update { s -> s.copy(subsAppSortType = sortOption.value) } }, ) } @@ -217,11 +218,11 @@ fun SubsPage( }, trailingIcon = { Checkbox(checked = showUninstallApp, onCheckedChange = { - vm.showUninstallAppFlow.value = it + storeFlow.update { s -> s.copy(subsAppShowUninstallApp = it) } }) }, onClick = { - vm.showUninstallAppFlow.value = !showUninstallApp + storeFlow.update { s -> s.copy(subsAppShowUninstallApp = !showUninstallApp) } }, ) } @@ -252,7 +253,8 @@ fun SubsPage( subsConfig = subsConfig, enableSize = enableSize, onClick = throttle { - navController.toDestinationsNavigator().navigate(AppItemPageDestination(subsItemId, appRaw.id)) + navController.toDestinationsNavigator() + .navigate(AppItemPageDestination(subsItemId, appRaw.id)) }, onValueChange = throttle(fn = vm.viewModelScope.launchAsFn { enable -> val newItem = subsConfig?.copy( @@ -284,11 +286,13 @@ fun SubsPage( item { Spacer(modifier = Modifier.height(EmptyHeight)) if (appAndConfigs.isEmpty()) { - EmptyText(text = if (searchStr.isNotEmpty()) { - if (showUninstallApp) "暂无搜索结果" else "暂无搜索结果,请尝试修改筛选条件" - } else { - "暂无规则" - }) + EmptyText( + text = if (searchStr.isNotEmpty()) { + if (showUninstallApp) "暂无搜索结果" else "暂无搜索结果,请尝试修改筛选条件" + } else { + "暂无规则" + } + ) } else if (editable) { Spacer(modifier = Modifier.height(EmptyHeight)) } 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 4e3ae64..83aa9e8 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SubsVm.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SubsVm.kt @@ -17,12 +17,14 @@ import li.songe.gkd.db.DbSet import li.songe.gkd.util.SortTypeOption import li.songe.gkd.util.appInfoCacheFlow import li.songe.gkd.util.collator +import li.songe.gkd.util.findOption import li.songe.gkd.util.getGroupRawEnable import li.songe.gkd.util.map +import li.songe.gkd.util.storeFlow import li.songe.gkd.util.subsIdToRawFlow import li.songe.gkd.util.subsItemsFlow -class SubsVm (stateHandle: SavedStateHandle) : ViewModel() { +class SubsVm(stateHandle: SavedStateHandle) : ViewModel() { private val args = SubsPageDestination.argsFrom(stateHandle) val subsItemFlow = @@ -43,9 +45,9 @@ class SubsVm (stateHandle: SavedStateHandle) : ViewModel() { DbSet.clickLogDao.queryLatestUniqueAppIds(args.subsItemId).map { appIds -> appIds.mapIndexed { index, appId -> appId to index }.toMap() } - val sortTypeFlow = MutableStateFlow(SortTypeOption.SortByName) + val sortTypeFlow = storeFlow.map(viewModelScope) { SortTypeOption.allSubObject.findOption(it.subsAppSortType) } - val showUninstallAppFlow = MutableStateFlow(false) + val showUninstallAppFlow = storeFlow.map(viewModelScope) { it.subsAppShowUninstallApp } private val sortAppsFlow = combine(combine((subsRawFlow.combine(appInfoCacheFlow) { subs, appInfoCache -> (subs?.apps ?: emptyList()).sortedWith { a, b -> 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 6665ca7..9b53062 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 @@ -69,6 +69,7 @@ import li.songe.gkd.ui.style.appItemPadding import li.songe.gkd.ui.style.menuPadding import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.SortTypeOption +import li.songe.gkd.util.mapHashCode import li.songe.gkd.util.ruleSummaryFlow import li.songe.gkd.util.storeFlow import li.songe.gkd.util.throttle @@ -112,14 +113,16 @@ fun useAppListPage(): ScaffoldExt { }) val listState = rememberLazyListState() - var isFirstVisit by remember { mutableStateOf(false) } - LaunchedEffect(key1 = orderedAppInfos, block = { + var isFirstVisit by remember { mutableStateOf(true) } + LaunchedEffect( + key1 = orderedAppInfos.mapHashCode { it.id } + ) { if (isFirstVisit) { - listState.scrollToItem(0) + isFirstVisit = false } else { - isFirstVisit = true + listState.scrollToItem(0) } - }) + } val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() return ScaffoldExt( navItem = appListNav, @@ -265,7 +268,9 @@ fun useAppListPage(): ScaffoldExt { Row( modifier = Modifier .clickable(onClick = throttle { - navController.toDestinationsNavigator().navigate(AppConfigPageDestination(appInfo.id)) + navController + .toDestinationsNavigator() + .navigate(AppConfigPageDestination(appInfo.id)) }) .height(IntrinsicSize.Min) .appItemPadding(), diff --git a/app/src/main/kotlin/li/songe/gkd/util/Others.kt b/app/src/main/kotlin/li/songe/gkd/util/Others.kt new file mode 100644 index 0000000..9b93376 --- /dev/null +++ b/app/src/main/kotlin/li/songe/gkd/util/Others.kt @@ -0,0 +1,5 @@ +package li.songe.gkd.util + +inline fun Iterable.mapHashCode(transform: (T) -> R): Int { + return fold(0) { acc, t -> 31 * acc + transform(t).hashCode() } +} diff --git a/app/src/main/kotlin/li/songe/gkd/util/Store.kt b/app/src/main/kotlin/li/songe/gkd/util/Store.kt index b37bc0f..099d4f8 100644 --- a/app/src/main/kotlin/li/songe/gkd/util/Store.kt +++ b/app/src/main/kotlin/li/songe/gkd/util/Store.kt @@ -74,15 +74,21 @@ data class Store( val enableDarkTheme: Boolean? = null, val enableDynamicColor: Boolean = true, val enableAbFloatWindow: Boolean = true, - val sortType: Int = SortTypeOption.SortByName.value, - val showSystemApp: Boolean = true, - val showHiddenApp: Boolean = false, val showSaveSnapshotToast: Boolean = true, val useSystemToast: Boolean = false, val useCustomNotifText: Boolean = false, val customNotifText: String = "\${i}全局/\${k}应用/\${u}规则组/\${n}触发", val enableActivityLog: Boolean = false, val updateChannel: Int = if (META.versionName.contains("beta")) UpdateChannelOption.Beta.value else UpdateChannelOption.Stable.value, + val sortType: Int = SortTypeOption.SortByName.value, + val showSystemApp: Boolean = true, + val showHiddenApp: Boolean = false, + val appRuleSortType: Int = RuleSortOption.Default.value, + val subsAppSortType: Int = SortTypeOption.SortByName.value, + val subsAppShowUninstallApp: Boolean = false, + val subsExcludeSortType: Int = SortTypeOption.SortByName.value, + val subsExcludeShowSystemApp: Boolean = true, + val subsExcludeShowHiddenApp: Boolean = false, ) val storeFlow by lazy {