diff --git a/app/src/main/kotlin/li/songe/gkd/MainViewModel.kt b/app/src/main/kotlin/li/songe/gkd/MainViewModel.kt index 8f536ab..d04aae4 100644 --- a/app/src/main/kotlin/li/songe/gkd/MainViewModel.kt +++ b/app/src/main/kotlin/li/songe/gkd/MainViewModel.kt @@ -4,6 +4,10 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.blankj.utilcode.util.LogUtils import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import li.songe.gkd.data.RawSubscription import li.songe.gkd.data.SubsItem @@ -12,6 +16,7 @@ import li.songe.gkd.permission.authReasonFlow import li.songe.gkd.util.checkUpdate import li.songe.gkd.util.launchTry import li.songe.gkd.util.logZipDir +import li.songe.gkd.util.map import li.songe.gkd.util.newVersionApkDir import li.songe.gkd.util.snapshotZipDir import li.songe.gkd.util.storeFlow @@ -60,8 +65,25 @@ class MainViewModel : ViewModel() { } } } + + viewModelScope.launch { + storeFlow.map(viewModelScope) { s -> s.log2FileSwitch }.collect { + LogUtils.getConfig().isLog2FileSwitch = it + } + } } + val enableDarkThemeFlow = storeFlow.debounce(200).map { s -> s.enableDarkTheme }.stateIn( + viewModelScope, + SharingStarted.Eagerly, + storeFlow.value.enableDarkTheme + ) + val enableDynamicColorFlow = storeFlow.debounce(300).map { s -> s.enableDynamicColor }.stateIn( + viewModelScope, + SharingStarted.Eagerly, + storeFlow.value.enableDynamicColor + ) + override fun onCleared() { super.onCleared() 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 b99aa4c..5d0e6ee 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt @@ -45,6 +45,23 @@ data class RawSubscription( } } + val categoryToAppMap by lazy { + val map = mutableMapOf>() + categories.forEach { c -> + apps.forEach { a -> + if (a.groups.any { g -> g.name.startsWith(c.name) }) { + val list = map[c] + if (list == null) { + map[c] = mutableListOf(a) + } else { + list.add(a) + } + } + } + } + map + } + val groupToCategoryMap by lazy { val map = mutableMapOf() categoryToGroupsMap.forEach { (key, value) -> diff --git a/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt b/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt index 779b2da..62be719 100644 --- a/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt +++ b/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt @@ -47,7 +47,7 @@ import li.songe.gkd.shizuku.shizukuIsSafeOK import li.songe.gkd.shizuku.useSafeGetTasksFc import li.songe.gkd.shizuku.useSafeInputTapFc import li.songe.gkd.shizuku.useShizukuAliveState -import li.songe.gkd.ui.home.UpdateTimeOption +import li.songe.gkd.util.UpdateTimeOption import li.songe.gkd.util.VOLUME_CHANGED_ACTION import li.songe.gkd.util.checkSubsUpdate import li.songe.gkd.util.launchTry diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AboutPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/AboutPage.kt index 853326f..5720829 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AboutPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AboutPage.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootNavGraph import li.songe.gkd.BuildConfig +import li.songe.gkd.ui.style.itemPadding import li.songe.gkd.util.GIT_COMMIT_URL import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions @@ -71,7 +72,7 @@ fun AboutPage() { context.openUri(REPOSITORY_URL) } .fillMaxWidth() - .padding(16.dp, 12.dp) + .itemPadding() ) { Text( text = "开源地址", @@ -86,7 +87,7 @@ fun AboutPage() { Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp, 12.dp) + .itemPadding() ) { Text( text = "版本代码", @@ -101,7 +102,7 @@ fun AboutPage() { Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp, 12.dp) + .itemPadding() ) { Text( text = "版本名称", @@ -120,7 +121,7 @@ fun AboutPage() { context.openUri(GIT_COMMIT_URL) } .fillMaxWidth() - .padding(16.dp, 12.dp) + .itemPadding() ) { Text( text = "代码记录", @@ -136,7 +137,7 @@ fun AboutPage() { Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp, 12.dp) + .itemPadding() ) { Text( text = "构建时间", @@ -151,7 +152,7 @@ fun AboutPage() { Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp, 12.dp) + .itemPadding() ) { Text( text = "构建类型", diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AdvancedPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/AdvancedPage.kt index b163436..774cadf 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AdvancedPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AdvancedPage.kt @@ -73,6 +73,7 @@ import li.songe.gkd.ui.component.AuthCard import li.songe.gkd.ui.component.SettingItem import li.songe.gkd.ui.component.TextSwitch import li.songe.gkd.ui.destinations.SnapshotPageDestination +import li.songe.gkd.ui.style.itemPadding import li.songe.gkd.util.LocalLauncher import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions @@ -124,7 +125,7 @@ fun AdvancedPage() { ) { Text( text = "Shizuku", - modifier = Modifier.padding(16.dp, 12.dp), + modifier = Modifier.itemPadding(), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary, ) @@ -150,12 +151,12 @@ fun AdvancedPage() { Text( text = "HTTP服务", - modifier = Modifier.padding(16.dp, 12.dp), + modifier = Modifier.itemPadding(), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary, ) Row( - modifier = Modifier.padding(16.dp, 14.dp), + modifier = Modifier.itemPadding(), verticalAlignment = Alignment.CenterVertically ) { Column(modifier = Modifier.weight(1f)) { @@ -215,7 +216,7 @@ fun AdvancedPage() { Row( modifier = Modifier .clickable { showPortDlg = true } - .padding(16.dp, 12.dp), + .itemPadding(), verticalAlignment = Alignment.CenterVertically ) { Column(modifier = Modifier.weight(1f)) { @@ -245,7 +246,7 @@ fun AdvancedPage() { Text( text = "快照", - modifier = Modifier.padding(16.dp, 12.dp), + modifier = Modifier.itemPadding(), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary, ) @@ -335,7 +336,7 @@ fun AdvancedPage() { Text( text = "其它", - modifier = Modifier.padding(16.dp, 12.dp), + modifier = Modifier.itemPadding(), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary, ) 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 2150b66..20c9a5b 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AppConfigPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AppConfigPage.kt @@ -25,7 +25,6 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold @@ -58,8 +57,10 @@ import li.songe.gkd.data.stringify import li.songe.gkd.db.DbSet import li.songe.gkd.ui.destinations.AppItemPageDestination import li.songe.gkd.ui.destinations.GlobalRulePageDestination +import li.songe.gkd.ui.style.itemPadding import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions +import li.songe.gkd.util.RuleSortOption import li.songe.gkd.util.appInfoCacheFlow import li.songe.gkd.util.launchTry import li.songe.gkd.util.navigate @@ -80,7 +81,6 @@ fun AppConfigPage(appId: String) { var expanded by remember { mutableStateOf(false) } val listState = rememberLazyListState() var isFirstVisit by remember { mutableStateOf(true) } - globalGroups.map { g -> g.group } LaunchedEffect(globalGroups.size, appGroups.size, ruleSortType.value) { if (isFirstVisit) { isFirstVisit = false @@ -124,7 +124,7 @@ fun AppConfigPage(appId: String) { expanded = expanded, onDismissRequest = { expanded = false } ) { - RuleSortType.allSubObject.forEach { s -> + RuleSortOption.allSubObject.forEach { s -> DropdownMenuItem( text = { Row( @@ -250,7 +250,7 @@ private fun AppGroupCard( Row( modifier = Modifier .clickable(onClick = onClick) - .padding(10.dp, 6.dp), + .itemPadding(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { @@ -265,7 +265,8 @@ private fun AppGroupCard( maxLines = 1, softWrap = false, overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.bodyLarge ) if (group.valid) { if (!group.desc.isNullOrBlank()) { @@ -275,14 +276,15 @@ private fun AppGroupCard( softWrap = false, overflow = TextOverflow.Ellipsis, modifier = Modifier.fillMaxWidth(), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } else { Text( text = "暂无描述", modifier = Modifier.fillMaxWidth(), style = MaterialTheme.typography.bodyMedium, - color = LocalContentColor.current.copy(alpha = 0.5f) + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) ) } } else { 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 03ed9b0..e6545c8 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AppConfigVm.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AppConfigVm.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.stateIn import li.songe.gkd.data.SubsConfig import li.songe.gkd.db.DbSet import li.songe.gkd.ui.destinations.AppConfigPageDestination +import li.songe.gkd.util.RuleSortOption import li.songe.gkd.util.collator import li.songe.gkd.util.ruleSummaryFlow import javax.inject.Inject @@ -29,7 +30,7 @@ class AppConfigVm @Inject constructor(stateHandle: SavedStateHandle) : ViewModel SubsConfig.AppGroupType ) - val ruleSortTypeFlow = MutableStateFlow(RuleSortType.Default) + val ruleSortTypeFlow = MutableStateFlow(RuleSortOption.Default) val globalGroupsFlow = combine( ruleSummaryFlow.map { r -> r.globalGroups }, @@ -37,15 +38,15 @@ class AppConfigVm @Inject constructor(stateHandle: SavedStateHandle) : ViewModel latestGlobalLogsFlow ) { list, type, logs -> when (type) { - RuleSortType.Default -> list - RuleSortType.ByName -> list.sortedWith { a, b -> + RuleSortOption.Default -> list + RuleSortOption.ByName -> list.sortedWith { a, b -> collator.compare( a.group.name, b.group.name ) } - RuleSortType.ByTime -> list.sortedBy { a -> + RuleSortOption.ByTime -> list.sortedBy { a -> -(logs.find { c -> c.groupKey == a.group.key && c.subsId == a.subsItem.id }?.id ?: 0) } @@ -58,15 +59,15 @@ class AppConfigVm @Inject constructor(stateHandle: SavedStateHandle) : ViewModel latestAppLogsFlow ) { list, type, logs -> when (type) { - RuleSortType.Default -> list - RuleSortType.ByName -> list.sortedWith { a, b -> + RuleSortOption.Default -> list + RuleSortOption.ByName -> list.sortedWith { a, b -> collator.compare( a.group.name, b.group.name ) } - RuleSortType.ByTime -> list.sortedBy { a -> + RuleSortOption.ByTime -> list.sortedBy { a -> -(logs.find { c -> c.groupKey == a.group.key && c.subsId == a.subsItem.id }?.id ?: 0) } @@ -75,12 +76,3 @@ class AppConfigVm @Inject constructor(stateHandle: SavedStateHandle) : ViewModel } -sealed class RuleSortType(val value: Int, val label: String) { - data object Default : RuleSortType(0, "按订阅顺序") - data object ByTime : RuleSortType(1, "按触发时间") - data object ByName : RuleSortType(2, "按名称") - - companion object { - val allSubObject by lazy { arrayOf(Default, ByTime, ByName) } - } -} \ 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 694382d..871827a 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AppItemPage.kt @@ -27,7 +27,6 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold @@ -68,6 +67,7 @@ import li.songe.gkd.data.stringify import li.songe.gkd.db.DbSet import li.songe.gkd.ui.component.getDialogResult import li.songe.gkd.ui.destinations.GroupItemPageDestination +import li.songe.gkd.ui.style.itemPadding import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions import li.songe.gkd.util.appInfoCacheFlow @@ -166,7 +166,7 @@ fun AppItemPage( if (group.key == focusGroupKey) MaterialTheme.colorScheme.inversePrimary else Color.Transparent ) .clickable { setShowGroupItem(group) } - .padding(10.dp, 6.dp), + .itemPadding(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { @@ -181,7 +181,8 @@ fun AppItemPage( maxLines = 1, softWrap = false, overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.bodyLarge, ) if (group.valid) { if (!group.desc.isNullOrBlank()) { @@ -191,21 +192,22 @@ fun AppItemPage( softWrap = false, overflow = TextOverflow.Ellipsis, modifier = Modifier.fillMaxWidth(), - fontSize = 14.sp + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } else { Text( text = "暂无描述", modifier = Modifier.fillMaxWidth(), - fontSize = 14.sp, - color = LocalContentColor.current.copy(alpha = 0.5f) + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) ) } } else { Text( text = "非法选择器", modifier = Modifier.fillMaxWidth(), - fontSize = 14.sp, + style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error ) } @@ -343,13 +345,18 @@ fun AppItemPage( } showGroupItem?.let { showGroupItemVal -> - AlertDialog(modifier = Modifier.defaultMinSize(300.dp), + AlertDialog( + modifier = Modifier.defaultMinSize(300.dp), onDismissRequest = { setShowGroupItem(null) }, title = { - Text(text = showGroupItemVal.name) + Text(text = "规则组详情") }, text = { - Text(text = showGroupItemVal.desc ?: "") + Column { + Text(text = showGroupItemVal.name) + Spacer(modifier = Modifier.height(10.dp)) + Text(text = showGroupItemVal.desc ?: "") + } }, confirmButton = { if (showGroupItemVal.allExampleUrls.isNotEmpty()) { 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 30aae42..a51cf64 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/CategoryPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/CategoryPage.kt @@ -12,25 +12,19 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.selection.selectable -import androidx.compose.foundation.shape.RoundedCornerShape 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.material.icons.filled.Add import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.filled.UnfoldMore import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Card import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -47,8 +41,6 @@ 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 -import androidx.compose.ui.window.Dialog import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewModelScope import com.ramcosta.composedestinations.annotation.Destination @@ -58,18 +50,15 @@ import li.songe.gkd.data.CategoryConfig import li.songe.gkd.data.RawSubscription import li.songe.gkd.db.DbSet import li.songe.gkd.ui.component.getDialogResult +import li.songe.gkd.ui.style.itemPadding +import li.songe.gkd.util.EnableGroupOption import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions +import li.songe.gkd.util.findOption import li.songe.gkd.util.launchTry import li.songe.gkd.util.toast import li.songe.gkd.util.updateSubscription -val enableGroupRadioOptions = arrayOf( - "跟随订阅" to null, - "全部启用" to true, - "全部关闭" to false, -) - @RootNavGraph @Destination(style = ProfileTransitions::class) @Composable @@ -84,15 +73,13 @@ fun CategoryPage(subsItemId: Long) { var showAddDlg by remember { mutableStateOf(false) } - var editEnableCategory by remember { - mutableStateOf(null) - } val (editNameCategory, setEditNameCategory) = remember { mutableStateOf(null) } val categories = subsRaw?.categories ?: emptyList() val categoriesGroups = subsRaw?.categoryToGroupsMap ?: emptyMap() + val categoriesApps = subsRaw?.categoryToAppMap ?: emptyMap() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { @@ -120,35 +107,36 @@ fun CategoryPage(subsItemId: Long) { modifier = Modifier.padding(contentPadding) ) { items(categories, { it.key }) { category -> + var selectedExpanded by remember { mutableStateOf(false) } Row(modifier = Modifier - .clickable { - editEnableCategory = category - } - .padding(10.dp, 6.dp), verticalAlignment = Alignment.CenterVertically - ) { + .clickable { selectedExpanded = true } + .itemPadding(), + verticalAlignment = Alignment.CenterVertically) { val size = categoriesGroups[category]?.size ?: 0 Column(modifier = Modifier.weight(1f)) { Text( - text = category.name, fontSize = 18.sp + text = category.name, + style = MaterialTheme.typography.bodyLarge, ) if (size > 0) { + val appSize = categoriesApps[category]?.size ?: 0 Text( - text = "${size}规则组", - fontSize = 14.sp + text = "${appSize}应用/${size}规则组", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } else { Text( text = "暂无规则", - fontSize = 14.sp, - color = LocalContentColor.current.copy(alpha = 0.5f) + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) ) } } if (editable) { var expanded by remember { mutableStateOf(false) } Box( - modifier = Modifier - .wrapContentSize(Alignment.TopStart) + modifier = Modifier.wrapContentSize(Alignment.TopStart) ) { IconButton(onClick = { expanded = true @@ -158,45 +146,33 @@ fun CategoryPage(subsItemId: Long) { contentDescription = null, ) } - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - DropdownMenuItem( - text = { - Text(text = "编辑") - }, - onClick = { - expanded = false - setEditNameCategory(category) - } - ) - DropdownMenuItem( - text = { - Text(text = "删除", color = MaterialTheme.colorScheme.error) - }, - onClick = { - expanded = false - vm.viewModelScope.launchTry { - val result = getDialogResult( - "删除类别", - "是否删除类别 ${category.name} ?" - ) - if (!result) return@launchTry - subsItem?.apply { - updateSubscription(subsRaw!!.copy( - categories = subsRaw!!.categories.filter { c -> c.key != category.key } - )) - DbSet.subsItemDao.update(copy(mtime = System.currentTimeMillis())) - } - DbSet.categoryConfigDao.deleteByCategoryKey( - subsItemId, - category.key - ) - toast("删除成功") + DropdownMenu(expanded = expanded, + onDismissRequest = { expanded = false }) { + DropdownMenuItem(text = { + Text(text = "编辑") + }, onClick = { + expanded = false + setEditNameCategory(category) + }) + DropdownMenuItem(text = { + Text(text = "删除", color = MaterialTheme.colorScheme.error) + }, onClick = { + expanded = false + vm.viewModelScope.launchTry { + val result = getDialogResult( + "删除类别", "是否删除类别 ${category.name} ?" + ) + if (!result) return@launchTry + subsItem?.apply { + updateSubscription(subsRaw!!.copy(categories = subsRaw!!.categories.filter { c -> c.key != category.key })) + DbSet.subsItemDao.update(copy(mtime = System.currentTimeMillis())) } + DbSet.categoryConfigDao.deleteByCategoryKey( + subsItemId, category.key + ) + toast("删除成功") } - ) + }) } } @@ -210,19 +186,38 @@ fun CategoryPage(subsItemId: Long) { val enable = if (categoryConfig != null) categoryConfig.enable else category.enable Text( - text = enableGroupRadioOptions.find { e -> e.second == enable }?.first - ?: "", - fontSize = 14.sp + text = EnableGroupOption.allSubObject.findOption(enable).label, + style = MaterialTheme.typography.bodyMedium, ) Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = null + imageVector = Icons.Default.UnfoldMore, contentDescription = null ) + DropdownMenu(expanded = selectedExpanded, + onDismissRequest = { selectedExpanded = false }) { + EnableGroupOption.allSubObject.forEach { option -> + DropdownMenuItem( + text = { + Text(text = option.label) + }, + onClick = { + selectedExpanded = false + if (option.value != enable) { + vm.viewModelScope.launchTry(Dispatchers.IO) { + DbSet.categoryConfigDao.insert( + (categoryConfig ?: CategoryConfig( + enable = option.value, + subsItemId = subsItemId, + categoryKey = category.key + )).copy(enable = option.value) + ) + } + } + }, + ) + } + } } } - if (categories.lastOrNull() !== category) { - HorizontalDivider() - } } item { Spacer(modifier = Modifier.height(40.dp)) @@ -239,153 +234,90 @@ fun CategoryPage(subsItemId: Long) { } } - editEnableCategory?.let { category -> - val categoryConfig = - categoryConfigs.find { c -> c.categoryKey == category.key } - val enable = - if (categoryConfig != null) categoryConfig.enable else category.enable - Dialog(onDismissRequest = { editEnableCategory = null }) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - shape = RoundedCornerShape(16.dp), - ) { - 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( - selected = (option.second == enable), - onClick = onClick - ) - Text( - text = option.first, modifier = Modifier.padding(start = 16.dp) - ) - } - } - } - } - } - val subsRawVal = subsRaw if (editNameCategory != null && subsRawVal != null) { var source by remember { mutableStateOf(editNameCategory.name) } - AlertDialog( - title = { Text(text = "编辑类别") }, - text = { - OutlinedTextField( - value = source, - onValueChange = { source = it.trim() }, - modifier = Modifier.fillMaxWidth(), - placeholder = { Text(text = "请输入类别名称") }, - singleLine = true - ) - }, - onDismissRequest = { setEditNameCategory(null) }, - dismissButton = { - TextButton(onClick = { setEditNameCategory(null) }) { - Text(text = "取消") - } - }, - confirmButton = { - TextButton( - enabled = source.isNotBlank() && source != editNameCategory.name, - onClick = { - if (categories.any { c -> c.key != editNameCategory.key && c.name == source }) { - toast("不可添加同名类别") - return@TextButton - } - vm.viewModelScope.launchTry(Dispatchers.IO) { - subsItem?.apply { - updateSubscription(subsRawVal.copy( - categories = categories.toMutableList().apply { - val i = - categories.indexOfFirst { c -> c.key == editNameCategory.key } - if (i >= 0) { - set(i, editNameCategory.copy(name = source)) - } - } - )) - DbSet.subsItemDao.update(copy(mtime = System.currentTimeMillis())) - } - toast("修改成功") - setEditNameCategory(null) - } - } - ) { - Text(text = "确认") - } + AlertDialog(title = { Text(text = "编辑类别") }, text = { + OutlinedTextField( + value = source, + onValueChange = { source = it.trim() }, + modifier = Modifier.fillMaxWidth(), + placeholder = { Text(text = "请输入类别名称") }, + singleLine = true + ) + }, onDismissRequest = { setEditNameCategory(null) }, dismissButton = { + TextButton(onClick = { setEditNameCategory(null) }) { + Text(text = "取消") } - ) + }, confirmButton = { + TextButton(enabled = source.isNotBlank() && source != editNameCategory.name, onClick = { + if (categories.any { c -> c.key != editNameCategory.key && c.name == source }) { + toast("不可添加同名类别") + return@TextButton + } + vm.viewModelScope.launchTry(Dispatchers.IO) { + subsItem?.apply { + updateSubscription( + subsRawVal.copy(categories = categories.toMutableList() + .apply { + val i = + categories.indexOfFirst { c -> c.key == editNameCategory.key } + if (i >= 0) { + set(i, editNameCategory.copy(name = source)) + } + }) + ) + DbSet.subsItemDao.update(copy(mtime = System.currentTimeMillis())) + } + toast("修改成功") + setEditNameCategory(null) + } + }) { + Text(text = "确认") + } + }) } if (showAddDlg && subsRawVal != null) { var source by remember { mutableStateOf("") } - AlertDialog( - title = { Text(text = "添加类别") }, - text = { - OutlinedTextField( - value = source, - onValueChange = { source = it.trim() }, - modifier = Modifier.fillMaxWidth(), - placeholder = { Text(text = "请输入类别名称") }, - singleLine = true - ) - }, - onDismissRequest = { showAddDlg = false }, - dismissButton = { - TextButton(onClick = { showAddDlg = false }) { - Text(text = "取消") - } - }, - confirmButton = { - TextButton(enabled = source.isNotEmpty(), onClick = { - if (categories.any { c -> c.name == source }) { - toast("不可添加同名类别") - return@TextButton - } - showAddDlg = false - vm.viewModelScope.launchTry(Dispatchers.IO) { - subsItem?.apply { - updateSubscription(subsRawVal.copy( - categories = categories.toMutableList().apply { - add(RawSubscription.RawCategory( - key = (categories.maxOfOrNull { c -> c.key } ?: -1) + 1, - name = source, - enable = null - )) - } - )) - DbSet.subsItemDao.update(copy(mtime = System.currentTimeMillis())) - toast("添加成功") - } - } - }) { - Text(text = "确认") - } + AlertDialog(title = { Text(text = "添加类别") }, text = { + OutlinedTextField( + value = source, + onValueChange = { source = it.trim() }, + modifier = Modifier.fillMaxWidth(), + placeholder = { Text(text = "请输入类别名称") }, + singleLine = true + ) + }, onDismissRequest = { showAddDlg = false }, dismissButton = { + TextButton(onClick = { showAddDlg = false }) { + Text(text = "取消") } - ) + }, confirmButton = { + TextButton(enabled = source.isNotEmpty(), onClick = { + if (categories.any { c -> c.name == source }) { + toast("不可添加同名类别") + return@TextButton + } + showAddDlg = false + vm.viewModelScope.launchTry(Dispatchers.IO) { + subsItem?.apply { + updateSubscription( + subsRawVal.copy(categories = categories.toMutableList() + .apply { + add(RawSubscription.RawCategory(key = (categories.maxOfOrNull { c -> c.key } + ?: -1) + 1, name = source, enable = null)) + }) + ) + DbSet.subsItemDao.update(copy(mtime = System.currentTimeMillis())) + toast("添加成功") + } + } + }) { + Text(text = "确认") + } + }) } } \ No newline at end of file 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 20f0840..a18ec5b 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludePage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRuleExcludePage.kt @@ -31,7 +31,7 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold @@ -70,6 +70,7 @@ import li.songe.gkd.data.stringify import li.songe.gkd.db.DbSet import li.songe.gkd.service.launcherAppId import li.songe.gkd.ui.component.AppBarTextField +import li.songe.gkd.ui.style.itemPadding import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions import li.songe.gkd.util.SortTypeOption @@ -239,7 +240,7 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) { Row( modifier = Modifier .height(IntrinsicSize.Min) - .padding(10.dp, 6.dp), + .itemPadding(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { @@ -283,7 +284,7 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) { softWrap = false, overflow = TextOverflow.Ellipsis, modifier = Modifier.fillMaxWidth(), - style = LocalTextStyle.current.let { + style = MaterialTheme.typography.bodyLarge.let { if (appInfo.isSystem) { it.copy(textDecoration = TextDecoration.Underline) } else { @@ -296,7 +297,9 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) { maxLines = 1, softWrap = false, overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } 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 d6a3673..4370b56 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/GlobalRulePage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/GlobalRulePage.kt @@ -28,7 +28,6 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold @@ -53,7 +52,6 @@ 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.compose.ui.window.Dialog import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewModelScope @@ -68,6 +66,7 @@ import li.songe.gkd.db.DbSet import li.songe.gkd.ui.component.getDialogResult import li.songe.gkd.ui.destinations.GlobalRuleExcludePageDestination import li.songe.gkd.ui.destinations.GroupItemPageDestination +import li.songe.gkd.ui.style.itemPadding import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions import li.songe.gkd.util.encodeToJson5String @@ -139,7 +138,7 @@ fun GlobalRulePage(subsItemId: Long, focusGroupKey: Int? = null) { if (group.key == focusGroupKey) MaterialTheme.colorScheme.inversePrimary else Color.Transparent ) .clickable { setShowGroupItem(group) } - .padding(10.dp, 6.dp), + .itemPadding(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { @@ -154,7 +153,8 @@ fun GlobalRulePage(subsItemId: Long, focusGroupKey: Int? = null) { maxLines = 1, softWrap = false, overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.bodyLarge, ) if (group.valid) { if (!group.desc.isNullOrBlank()) { @@ -164,21 +164,22 @@ fun GlobalRulePage(subsItemId: Long, focusGroupKey: Int? = null) { softWrap = false, overflow = TextOverflow.Ellipsis, modifier = Modifier.fillMaxWidth(), - fontSize = 14.sp + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } else { Text( text = "暂无描述", modifier = Modifier.fillMaxWidth(), - fontSize = 14.sp, - color = LocalContentColor.current.copy(alpha = 0.5f) + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) ) } } else { Text( text = "非法选择器", modifier = Modifier.fillMaxWidth(), - fontSize = 14.sp, + style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error ) } @@ -449,10 +450,14 @@ fun GlobalRulePage(subsItemId: Long, focusGroupKey: Int? = null) { modifier = Modifier.defaultMinSize(300.dp), onDismissRequest = { setShowGroupItem(null) }, title = { - Text(text = showGroupItem.name) + Text(text = "规则组详情") }, text = { - Text(text = showGroupItem.desc ?: "") + Column { + Text(text = showGroupItem.name) + Spacer(modifier = Modifier.height(10.dp)) + Text(text = showGroupItem.desc ?: "") + } }, confirmButton = { if (showGroupItem.allExampleUrls.isNotEmpty()) { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/SlowGroupPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/SlowGroupPage.kt index c4b1823..2298759 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SlowGroupPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SlowGroupPage.kt @@ -17,6 +17,7 @@ import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -34,11 +35,11 @@ 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.ui.style.itemPadding import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions import li.songe.gkd.util.appInfoCacheFlow @@ -98,7 +99,7 @@ fun SlowGroupPage() { ) ) } - .padding(10.dp, 5.dp), + .itemPadding(), title = group.name, desc = "${rule.rawSubs.name}/全局规则" ) @@ -118,12 +119,12 @@ fun SlowGroupPage() { ) ) } - .padding(10.dp, 5.dp), + .itemPadding(), title = group.name, desc = "${rule.rawSubs.name}/应用规则/${appInfoCache[rule.app.id]?.name ?: rule.app.name ?: rule.app.id}" ) } - item("empty") { + item { Spacer(modifier = Modifier.height(40.dp)) if (ruleSummary.slowGroupCount == 0) { Text( @@ -160,17 +161,18 @@ fun SlowGroupCard(title: String, desc: String, modifier: Modifier = Modifier) { Column(modifier = Modifier.weight(1f)) { Text( text = title, - fontSize = 18.sp, + style = MaterialTheme.typography.bodyLarge, maxLines = 1, softWrap = false, overflow = TextOverflow.Ellipsis, ) Text( text = desc, - fontSize = 14.sp, + style = MaterialTheme.typography.bodyMedium, maxLines = 1, softWrap = false, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } Icon( 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 ea62faf..a478616 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SubsPage.kt @@ -259,19 +259,17 @@ fun SubsPage( DbSet.subsConfigDao.insert(newItem) }, showMenu = editable, - onDelClick = { - vm.viewModelScope.launchTry { - val result = getDialogResult( - "删除规则组", - "确定删除 ${appInfoCache[appRaw.id]?.name ?: appRaw.name ?: appRaw.id} 下所有规则组?" - ) - if (!result) return@launchTry - if (subsRaw != null && subsItem != null) { - updateSubscription(subsRaw.copy(apps = subsRaw.apps.filter { a -> a.id != appRaw.id })) - DbSet.subsItemDao.update(subsItem.copy(mtime = System.currentTimeMillis())) - DbSet.subsConfigDao.delete(subsItem.id, appRaw.id) - toast("删除成功") - } + onDelClick = vm.viewModelScope.launchAsFn { + val result = getDialogResult( + "删除规则组", + "确定删除 ${appInfoCache[appRaw.id]?.name ?: appRaw.name ?: appRaw.id} 下所有规则组?" + ) + if (!result) return@launchAsFn + if (subsRaw != null && subsItem != null) { + updateSubscription(subsRaw.copy(apps = subsRaw.apps.filter { a -> a.id != appRaw.id })) + DbSet.subsItemDao.update(subsItem.copy(mtime = System.currentTimeMillis())) + DbSet.subsConfigDao.delete(subsItem.id, appRaw.id) + toast("删除成功") } }) } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/component/AuthCard.kt b/app/src/main/kotlin/li/songe/gkd/ui/component/AuthCard.kt index 31cae00..dccdb7a 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/component/AuthCard.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/component/AuthCard.kt @@ -3,7 +3,6 @@ package li.songe.gkd.ui.component import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton @@ -12,6 +11,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import li.songe.gkd.ui.style.itemPadding @Composable fun AuthCard( @@ -20,7 +20,7 @@ fun AuthCard( onAuthClick: () -> Unit, ) { Row( - modifier = Modifier.padding(16.dp, 12.dp), + modifier = Modifier.itemPadding(), verticalAlignment = Alignment.CenterVertically ) { Column(modifier = Modifier.weight(1f)) { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/component/RotatingLoadingIcon.kt b/app/src/main/kotlin/li/songe/gkd/ui/component/RotatingLoadingIcon.kt new file mode 100644 index 0000000..c0928bb --- /dev/null +++ b/app/src/main/kotlin/li/songe/gkd/ui/component/RotatingLoadingIcon.kt @@ -0,0 +1,53 @@ +package li.songe.gkd.ui.component + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.tween +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Autorenew +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import kotlin.math.sin + +@Composable +fun RotatingLoadingIcon(loading: Boolean) { + val rotation = remember { Animatable(0f) } + LaunchedEffect(loading) { + if (loading) { + rotation.animateTo( + targetValue = rotation.value + 180f, + animationSpec = tween( + durationMillis = 250, + easing = { x -> sin(Math.PI / 2 * (x - 1f)).toFloat() + 1f } + ) + ) + rotation.animateTo( + targetValue = rotation.value + 360f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 500, easing = LinearEasing), + repeatMode = RepeatMode.Restart + ) + ) + } else if (rotation.value != 0f) { + rotation.animateTo( + targetValue = rotation.value + 180f, + animationSpec = tween( + durationMillis = 250, + easing = { x -> sin(Math.PI / 2 * x).toFloat() } + ) + ) + } + } + Icon( + imageVector = Icons.Default.Autorenew, + contentDescription = null, + modifier = Modifier.graphicsLayer(rotationZ = rotation.value) + ) +} + diff --git a/app/src/main/kotlin/li/songe/gkd/ui/component/SettingItem.kt b/app/src/main/kotlin/li/songe/gkd/ui/component/SettingItem.kt index 7c442c3..c0e68ec 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/component/SettingItem.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/component/SettingItem.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material3.Icon @@ -14,7 +13,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.unit.dp +import li.songe.gkd.ui.style.itemPadding @Composable fun SettingItem( @@ -28,7 +27,7 @@ fun SettingItem( onClick = onClick ) .fillMaxWidth() - .padding(16.dp, 12.dp), + .itemPadding(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/component/SubsAppCard.kt b/app/src/main/kotlin/li/songe/gkd/ui/component/SubsAppCard.kt index 8b1940a..1101597 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/component/SubsAppCard.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/component/SubsAppCard.kt @@ -22,8 +22,6 @@ import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.Text @@ -109,7 +107,7 @@ fun SubsAppCard( softWrap = false, overflow = TextOverflow.Ellipsis, modifier = Modifier.fillMaxWidth(), - style = LocalTextStyle.current.let { + style = MaterialTheme.typography.bodyLarge.let { if (appInfo?.isSystem == true) { it.copy(textDecoration = TextDecoration.Underline) } else { @@ -129,13 +127,16 @@ fun SubsAppCard( maxLines = 1, softWrap = false, overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } else { Text( text = "暂无规则", modifier = Modifier.fillMaxWidth(), - color = LocalContentColor.current.copy(alpha = 0.5f) + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f), ) } } diff --git a/app/src/main/kotlin/li/songe/gkd/ui/component/TextMenu.kt b/app/src/main/kotlin/li/songe/gkd/ui/component/TextMenu.kt new file mode 100644 index 0000000..f8d1da7 --- /dev/null +++ b/app/src/main/kotlin/li/songe/gkd/ui/component/TextMenu.kt @@ -0,0 +1,77 @@ +package li.songe.gkd.ui.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.UnfoldMore +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import li.songe.gkd.ui.style.itemPadding +import li.songe.gkd.util.Option +import li.songe.gkd.util.allSubObject + +@Composable +fun TextMenu( + title: String, + option: Option, + onOptionChange: ((Option) -> Unit), +) { + var expanded by remember { mutableStateOf(false) } + Row( + modifier = Modifier + .clickable { + expanded = true + } + .fillMaxWidth() + .itemPadding(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = title, + style = MaterialTheme.typography.bodyLarge, + ) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = option.label, + style = MaterialTheme.typography.bodyMedium, + ) + Icon( + imageVector = Icons.Default.UnfoldMore, + contentDescription = null + ) + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + option.allSubObject.forEach { otherOption -> + DropdownMenuItem( + text = { + Text(text = otherOption.label) + }, + onClick = { + expanded = false + if (otherOption != option) { + onOptionChange(otherOption) + } + }, + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/li/songe/gkd/ui/component/TextSwitch.kt b/app/src/main/kotlin/li/songe/gkd/ui/component/TextSwitch.kt index e77cd36..9abb8e1 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/component/TextSwitch.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/component/TextSwitch.kt @@ -3,7 +3,6 @@ package li.songe.gkd.ui.component import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch @@ -12,30 +11,38 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import li.songe.gkd.ui.style.itemPadding @Composable fun TextSwitch( modifier: Modifier = Modifier, - name: String = "", - desc: String = "", + name: String, + desc: String? = null, checked: Boolean = true, enabled: Boolean = true, onCheckedChange: ((Boolean) -> Unit)? = null, ) { Row( - modifier = modifier.padding(16.dp, 12.dp), + modifier = modifier.itemPadding(), verticalAlignment = Alignment.CenterVertically ) { - Column(modifier = Modifier.weight(1f)) { + if (desc != null) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = name, + style = MaterialTheme.typography.bodyLarge, + ) + Text( + text = desc, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } else { Text( - name, + text = name, style = MaterialTheme.typography.bodyLarge, ) - Text( - desc, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) } Spacer(modifier = Modifier.width(10.dp)) Switch( 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 8dd422b..91d6534 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 @@ -42,6 +42,7 @@ 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.ui.style.itemPadding import li.songe.gkd.util.HOME_PAGE_URL import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.launchAsFn @@ -131,7 +132,7 @@ fun useControlPage(): ScaffoldExt { .clickable { context.openUri(HOME_PAGE_URL) } - .padding(16.dp, 12.dp), + .itemPadding(), ) { Column(modifier = Modifier.weight(1f)) { Text( @@ -158,7 +159,7 @@ fun useControlPage(): ScaffoldExt { .clickable { navController.navigate(ClickLogPageDestination) } - .padding(16.dp, 12.dp), + .itemPadding(), ) { Column(modifier = Modifier.weight(1f)) { Text( @@ -185,7 +186,7 @@ fun useControlPage(): ScaffoldExt { .clickable { navController.navigate(SlowGroupPageDestination) } - .padding(16.dp, 12.dp), + .itemPadding(), ) { Column(modifier = Modifier.weight(1f)) { Text( @@ -208,7 +209,7 @@ fun useControlPage(): ScaffoldExt { Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp, 12.dp) + .itemPadding() ) { Text( text = subsStatus, 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 fe09ba3..3cd83d4 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 @@ -1,10 +1,5 @@ package li.songe.gkd.ui.home -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.tween import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -14,26 +9,21 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight -import androidx.compose.material.icons.filled.Autorenew +import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.outlined.Settings import androidx.compose.material3.AlertDialog import androidx.compose.material3.Card -import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.RadioButton 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.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -41,7 +31,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign @@ -56,22 +45,28 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.update import li.songe.gkd.MainActivity import li.songe.gkd.appScope +import li.songe.gkd.ui.component.RotatingLoadingIcon import li.songe.gkd.ui.component.SettingItem +import li.songe.gkd.ui.component.TextMenu import li.songe.gkd.ui.component.TextSwitch import li.songe.gkd.ui.destinations.AboutPageDestination import li.songe.gkd.ui.destinations.AdvancedPageDestination +import li.songe.gkd.ui.style.itemPadding +import li.songe.gkd.ui.theme.supportDynamicColor +import li.songe.gkd.util.DarkThemeOption import li.songe.gkd.util.LoadStatus import li.songe.gkd.util.LocalNavController +import li.songe.gkd.util.UpdateTimeOption import li.songe.gkd.util.buildLogFile import li.songe.gkd.util.checkUpdate import li.songe.gkd.util.checkUpdatingFlow +import li.songe.gkd.util.findOption import li.songe.gkd.util.launchAsFn import li.songe.gkd.util.launchTry import li.songe.gkd.util.navigate import li.songe.gkd.util.shareFile import li.songe.gkd.util.storeFlow import li.songe.gkd.util.toast -import kotlin.math.sin val settingsNav = BottomNavItem( label = "设置", icon = Icons.Outlined.Settings @@ -85,12 +80,6 @@ fun useSettingsPage(): ScaffoldExt { val vm = hiltViewModel() val uploadStatus by vm.uploadStatusFlow.collectAsState() - var showSubsIntervalDlg by remember { - mutableStateOf(false) - } - var showEnableDarkThemeDlg by remember { - mutableStateOf(false) - } var showToastInputDlg by remember { mutableStateOf(false) } @@ -101,74 +90,6 @@ fun useSettingsPage(): ScaffoldExt { val checkUpdating by checkUpdatingFlow.collectAsState() - if (showSubsIntervalDlg) { - Dialog(onDismissRequest = { showSubsIntervalDlg = false }) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - shape = RoundedCornerShape(16.dp), - ) { - UpdateTimeOption.allSubObject.forEach { option -> - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .selectable( - selected = (option.value == store.updateSubsInterval), - onClick = { - storeFlow.update { it.copy(updateSubsInterval = option.value) } - }) - .padding(horizontal = 16.dp) - ) { - RadioButton( - selected = (option.value == store.updateSubsInterval), - onClick = { - storeFlow.update { it.copy(updateSubsInterval = option.value) } - }) - Text( - text = option.label, modifier = Modifier.padding(start = 16.dp) - ) - } - } - } - } - } - - if (showEnableDarkThemeDlg) { - Dialog(onDismissRequest = { showEnableDarkThemeDlg = false }) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - shape = RoundedCornerShape(16.dp), - ) { - 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), - onClick = { - storeFlow.value = store.copy(enableDarkTheme = option.second) - }) - Text( - text = option.first, modifier = Modifier.padding(start = 16.dp) - ) - } - } - } - } - } - if (showToastInputDlg) { var value by remember { mutableStateOf(store.clickToast) @@ -335,12 +256,13 @@ fun useSettingsPage(): ScaffoldExt { Text( text = "常规", - modifier = Modifier.padding(16.dp, 12.dp), + modifier = Modifier.itemPadding(), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary, ) - TextSwitch(name = "触发提示", + TextSwitch( + name = "触发提示", desc = store.clickToast, checked = store.toastWhenClick, modifier = Modifier.clickable { @@ -350,83 +272,57 @@ fun useSettingsPage(): ScaffoldExt { storeFlow.value = store.copy( toastWhenClick = it ) - }) + } + ) TextSwitch( name = "后台隐藏", - desc = "在[最近任务]界面中隐藏本应用", + desc = "在[最近任务]中隐藏本应用", checked = store.excludeFromRecents, onCheckedChange = { storeFlow.value = store.copy( excludeFromRecents = it ) - }) - - Row( - modifier = Modifier - .clickable { - showEnableDarkThemeDlg = true - } - .padding(16.dp, 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - modifier = Modifier.weight(1f), - text = "深色模式", - style = MaterialTheme.typography.bodyLarge, - ) - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = darkThemeRadioOptions.find { it.second == store.enableDarkTheme }?.first - ?: store.enableDarkTheme.toString(), - style = MaterialTheme.typography.bodyMedium, - ) - Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = "more" - ) } + ) + + TextMenu( + title = "深色模式", + option = DarkThemeOption.allSubObject.findOption(store.enableDarkTheme) + ) { + storeFlow.update { s -> s.copy(enableDarkTheme = it.value) } + } + + if (supportDynamicColor) { + TextSwitch( + name = "动态配色", + desc = "配色跟随系统主题", + checked = store.enableDynamicColor, + onCheckedChange = { + storeFlow.value = store.copy( + enableDynamicColor = it + ) + } + ) } Text( text = "更新", - modifier = Modifier.padding(16.dp, 12.dp), + modifier = Modifier.itemPadding(), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary, ) - Row( - modifier = Modifier - .clickable { - showSubsIntervalDlg = true - } - .padding(16.dp, 12.dp), - verticalAlignment = Alignment.CenterVertically + TextMenu( + title = "更新订阅", + option = UpdateTimeOption.allSubObject.findOption(store.updateSubsInterval) ) { - Text( - modifier = Modifier.weight(1f), - text = "自动更新订阅", - style = MaterialTheme.typography.bodyLarge, - ) - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = UpdateTimeOption.allSubObject.find { it.value == store.updateSubsInterval }?.label - ?: store.updateSubsInterval.toString(), - style = MaterialTheme.typography.bodyMedium, - ) - Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = "more" - ) - } + storeFlow.update { s -> s.copy(updateSubsInterval = it.value) } } - TextSwitch(name = "自动更新应用", - desc = "打开应用时自动检测是否存在新版本", + TextSwitch( + name = "自动更新", + desc = "打开应用时检测新版本", checked = store.autoCheckAppUpdate, onCheckedChange = { storeFlow.value = store.copy( @@ -446,7 +342,7 @@ fun useSettingsPage(): ScaffoldExt { } ) .fillMaxWidth() - .padding(16.dp, 12.dp), + .itemPadding(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { @@ -459,13 +355,14 @@ fun useSettingsPage(): ScaffoldExt { Text( text = "日志", - modifier = Modifier.padding(16.dp, 12.dp), + modifier = Modifier.itemPadding(), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary, ) - TextSwitch(name = "保存日志", - desc = "保存最近7天日志,关闭后无法定位解决错误", + TextSwitch( + name = "保存日志", + desc = "保存7天日志,帮助定位BUG", checked = store.log2FileSwitch, onCheckedChange = { storeFlow.value = store.copy( @@ -485,13 +382,17 @@ fun useSettingsPage(): ScaffoldExt { } ) - SettingItem(title = "分享日志", onClick = { - showShareLogDlg = true - }) + SettingItem( + title = "分享日志", + imageVector = Icons.Default.Share, + onClick = { + showShareLogDlg = true + } + ) Text( text = "其它", - modifier = Modifier.padding(16.dp, 12.dp), + modifier = Modifier.itemPadding(), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary, ) @@ -508,57 +409,3 @@ fun useSettingsPage(): ScaffoldExt { } } } - -sealed class UpdateTimeOption(val value: Long, val label: String) { - data object Pause : UpdateTimeOption(-1, "暂停") - data object Everyday : UpdateTimeOption(24 * 60 * 60_000, "每天") - data object Every3Days : UpdateTimeOption(24 * 60 * 60_000 * 3, "每3天") - data object Every7Days : UpdateTimeOption(24 * 60 * 60_000 * 7, "每7天") - - companion object { - val allSubObject by lazy { arrayOf(Pause, Everyday, Every3Days, Every7Days) } - } -} - -private val darkThemeRadioOptions = arrayOf( - "跟随系统" to null, - "启用" to true, - "关闭" to false, -) - -@Composable -fun RotatingLoadingIcon(loading: Boolean) { - val rotation = remember { Animatable(0f) } - LaunchedEffect(loading) { - if (loading) { - rotation.animateTo( - targetValue = rotation.value + 180f, - animationSpec = tween( - durationMillis = 250, - easing = { x -> sin(Math.PI / 2 * (x - 1f)).toFloat() + 1f } - ) - ) - rotation.animateTo( - targetValue = rotation.value + 360f, - animationSpec = infiniteRepeatable( - animation = tween(durationMillis = 500, easing = LinearEasing), - repeatMode = RepeatMode.Restart - ) - ) - } else if (rotation.value != 0f) { - rotation.animateTo( - targetValue = rotation.value + 180f, - animationSpec = tween( - durationMillis = 250, - easing = { x -> sin(Math.PI / 2 * x).toFloat() } - ) - ) - } - } - Icon( - imageVector = Icons.Default.Autorenew, - contentDescription = null, - modifier = Modifier.graphicsLayer(rotationZ = rotation.value) - ) -} - diff --git a/app/src/main/kotlin/li/songe/gkd/ui/style/Padding.kt b/app/src/main/kotlin/li/songe/gkd/ui/style/Padding.kt new file mode 100644 index 0000000..02d5207 --- /dev/null +++ b/app/src/main/kotlin/li/songe/gkd/ui/style/Padding.kt @@ -0,0 +1,7 @@ +package li.songe.gkd.ui.style + +import androidx.compose.foundation.layout.padding +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +fun Modifier.itemPadding() = this then padding(16.dp, 12.dp) diff --git a/app/src/main/kotlin/li/songe/gkd/ui/theme/Theme.kt b/app/src/main/kotlin/li/songe/gkd/ui/theme/Theme.kt index e5bcb25..4c00c95 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/theme/Theme.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/theme/Theme.kt @@ -1,7 +1,6 @@ package li.songe.gkd.ui.theme import android.os.Build -import androidx.activity.ComponentActivity import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme @@ -12,33 +11,33 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext import androidx.core.view.WindowInsetsControllerCompat -import li.songe.gkd.util.map -import li.songe.gkd.util.storeFlow +import li.songe.gkd.MainActivity import li.songe.gkd.util.updateToastStyle val LightColorScheme = lightColorScheme() val DarkColorScheme = darkColorScheme() +val supportDynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S @Composable fun AppTheme( content: @Composable () -> Unit, ) { // https://developer.android.com/jetpack/compose/designsystems/material3?hl=zh-cn - val context = LocalContext.current as ComponentActivity - val scope = rememberCoroutineScope() - val enableDarkTheme by storeFlow.map(scope) { s -> s.enableDarkTheme }.collectAsState() + val context = LocalContext.current as MainActivity + val enableDarkTheme by context.mainVm.enableDarkThemeFlow.collectAsState() + val enableDynamicColor by context.mainVm.enableDynamicColorFlow.collectAsState() val systemInDarkTheme = isSystemInDarkTheme() val darkTheme = enableDarkTheme ?: systemInDarkTheme - val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S val colorScheme = when { - dynamicColor && darkTheme -> dynamicDarkColorScheme(context) - dynamicColor && !darkTheme -> dynamicLightColorScheme(context) + supportDynamicColor && enableDynamicColor && darkTheme -> dynamicDarkColorScheme(context) + supportDynamicColor && enableDynamicColor && !darkTheme -> dynamicLightColorScheme(context) darkTheme -> DarkColorScheme else -> LightColorScheme } + + // https://github.com/gkd-kit/gkd/pull/421 LaunchedEffect(darkTheme) { WindowInsetsControllerCompat(context.window, context.window.decorView).apply { isAppearanceLightStatusBars = !darkTheme diff --git a/app/src/main/kotlin/li/songe/gkd/util/Option.kt b/app/src/main/kotlin/li/songe/gkd/util/Option.kt new file mode 100644 index 0000000..8c5000f --- /dev/null +++ b/app/src/main/kotlin/li/songe/gkd/util/Option.kt @@ -0,0 +1,74 @@ +package li.songe.gkd.util + +sealed interface Option { + val value: T + val label: String +} + +fun > Array.findOption(value: V): T { + return find { it.value == value } ?: first() +} + +@Suppress("UNCHECKED_CAST") +val Option.allSubObject: Array> + get() = when (this) { + is SortTypeOption -> SortTypeOption.allSubObject + is UpdateTimeOption -> UpdateTimeOption.allSubObject + is DarkThemeOption -> DarkThemeOption.allSubObject + is EnableGroupOption -> EnableGroupOption.allSubObject + is RuleSortOption -> RuleSortOption.allSubObject + } as Array> + +sealed class SortTypeOption(override val value: Int, override val label: String) : Option { + data object SortByName : SortTypeOption(0, "按名称") + data object SortByAppMtime : SortTypeOption(1, "按更新时间") + data object SortByTriggerTime : SortTypeOption(2, "按触发时间") + + companion object { + // https://stackoverflow.com/questions/47648689 + val allSubObject by lazy { arrayOf(SortByName, SortByAppMtime, SortByTriggerTime) } + } +} + +sealed class UpdateTimeOption(override val value: Long, override val label: String) : Option { + data object Pause : UpdateTimeOption(-1, "暂停") + data object Everyday : UpdateTimeOption(24 * 60 * 60_000, "每天") + data object Every3Days : UpdateTimeOption(24 * 60 * 60_000 * 3, "每3天") + data object Every7Days : UpdateTimeOption(24 * 60 * 60_000 * 7, "每7天") + + companion object { + val allSubObject by lazy { arrayOf(Pause, Everyday, Every3Days, Every7Days) } + } +} + +sealed class DarkThemeOption(override val value: Boolean?, override val label: String) : + Option { + data object FollowSystem : DarkThemeOption(null, "跟随系统") + data object AlwaysEnable : DarkThemeOption(true, "总是启用") + data object AlwaysDisable : DarkThemeOption(false, "总是关闭") + + companion object { + val allSubObject by lazy { arrayOf(FollowSystem, AlwaysEnable, AlwaysDisable) } + } +} + +sealed class EnableGroupOption(override val value: Boolean?, override val label: String) : + Option { + data object FollowSubs : DarkThemeOption(null, "跟随订阅") + data object AllEnable : DarkThemeOption(true, "全部启用") + data object AllDisable : DarkThemeOption(false, "全部关闭") + + companion object { + val allSubObject by lazy { arrayOf(FollowSubs, AllEnable, AllDisable) } + } +} + +sealed class RuleSortOption(override val value: Int, override val label: String) : Option { + data object Default : RuleSortOption(0, "按订阅顺序") + data object ByTime : RuleSortOption(1, "按触发时间") + data object ByName : RuleSortOption(2, "按名称") + + companion object { + val allSubObject by lazy { arrayOf(Default, ByTime, ByName) } + } +} diff --git a/app/src/main/kotlin/li/songe/gkd/util/SortTypeOption.kt b/app/src/main/kotlin/li/songe/gkd/util/SortTypeOption.kt deleted file mode 100644 index bf193bb..0000000 --- a/app/src/main/kotlin/li/songe/gkd/util/SortTypeOption.kt +++ /dev/null @@ -1,13 +0,0 @@ -package li.songe.gkd.util - - -sealed class SortTypeOption(val value: Int, val label: String) { - data object SortByName : SortTypeOption(0, "按名称") - data object SortByAppMtime : SortTypeOption(1, "按更新时间") - data object SortByTriggerTime : SortTypeOption(2, "按触发时间") - - companion object { - // https://stackoverflow.com/questions/47648689 - val allSubObject by lazy { arrayOf(SortByName, SortByAppMtime, SortByTriggerTime) } - } -} \ No newline at end of file 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 d05836e..ff856dd 100644 --- a/app/src/main/kotlin/li/songe/gkd/util/Store.kt +++ b/app/src/main/kotlin/li/songe/gkd/util/Store.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import li.songe.gkd.appScope -import li.songe.gkd.ui.home.UpdateTimeOption private inline fun createStorageFlow( key: String, @@ -57,6 +56,7 @@ data class Store( val enableShizukuClick: Boolean = false, val log2FileSwitch: Boolean = true, val enableDarkTheme: Boolean? = null, + val enableDynamicColor: Boolean = true, val enableAbFloatWindow: Boolean = true, val sortType: Int = SortTypeOption.SortByName.value, val showSystemApp: Boolean = true, @@ -88,8 +88,6 @@ val clickCountFlow by lazy { recordStoreFlow.map(appScope) { r -> r.clickCount } } -private val log2FileSwitchFlow by lazy { storeFlow.map(appScope) { s -> s.log2FileSwitch } } - fun increaseClickCount(n: Int = 1) { recordStoreFlow.update { it.copy( @@ -101,10 +99,5 @@ fun increaseClickCount(n: Int = 1) { fun initStore() { storeFlow.value recordStoreFlow.value - appScope.launchTry(Dispatchers.IO) { - log2FileSwitchFlow.collect { - LogUtils.getConfig().isLog2FileSwitch = it - } - } }