mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-16 11:42:22 +08:00
refactor: ui, padding, text
This commit is contained in:
parent
96ac199f1a
commit
30da7f6ccb
|
@ -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()
|
||||
|
|
|
@ -45,6 +45,23 @@ data class RawSubscription(
|
|||
}
|
||||
}
|
||||
|
||||
val categoryToAppMap by lazy {
|
||||
val map = mutableMapOf<RawCategory, MutableList<RawApp>>()
|
||||
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<RawAppGroup, RawCategory>()
|
||||
categoryToGroupsMap.forEach { (key, value) ->
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = "构建类型",
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>(RuleSortType.Default)
|
||||
val ruleSortTypeFlow = MutableStateFlow<RuleSortOption>(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) }
|
||||
}
|
||||
}
|
|
@ -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()) {
|
||||
|
|
|
@ -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<RawSubscription.RawCategory?>(null)
|
||||
}
|
||||
val (editNameCategory, setEditNameCategory) = remember {
|
||||
mutableStateOf<RawSubscription.RawCategory?>(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 = "确认")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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("删除成功")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
77
app/src/main/kotlin/li/songe/gkd/ui/component/TextMenu.kt
Normal file
77
app/src/main/kotlin/li/songe/gkd/ui/component/TextMenu.kt
Normal file
|
@ -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 <T> TextMenu(
|
||||
title: String,
|
||||
option: Option<T>,
|
||||
onOptionChange: ((Option<T>) -> 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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<HomeVm>()
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
7
app/src/main/kotlin/li/songe/gkd/ui/style/Padding.kt
Normal file
7
app/src/main/kotlin/li/songe/gkd/ui/style/Padding.kt
Normal file
|
@ -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)
|
|
@ -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
|
||||
|
|
74
app/src/main/kotlin/li/songe/gkd/util/Option.kt
Normal file
74
app/src/main/kotlin/li/songe/gkd/util/Option.kt
Normal file
|
@ -0,0 +1,74 @@
|
|||
package li.songe.gkd.util
|
||||
|
||||
sealed interface Option<T> {
|
||||
val value: T
|
||||
val label: String
|
||||
}
|
||||
|
||||
fun <V, T : Option<V>> Array<T>.findOption(value: V): T {
|
||||
return find { it.value == value } ?: first()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val <T> Option<T>.allSubObject: Array<Option<T>>
|
||||
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<Option<T>>
|
||||
|
||||
sealed class SortTypeOption(override val value: Int, override val label: String) : Option<Int> {
|
||||
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<Long> {
|
||||
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<Boolean?> {
|
||||
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<Boolean?> {
|
||||
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<Int> {
|
||||
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) }
|
||||
}
|
||||
}
|
|
@ -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) }
|
||||
}
|
||||
}
|
|
@ -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 <reified T> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user