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 androidx.lifecycle.viewModelScope
|
||||||
import com.blankj.utilcode.util.LogUtils
|
import com.blankj.utilcode.util.LogUtils
|
||||||
import kotlinx.coroutines.Dispatchers
|
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 kotlinx.coroutines.launch
|
||||||
import li.songe.gkd.data.RawSubscription
|
import li.songe.gkd.data.RawSubscription
|
||||||
import li.songe.gkd.data.SubsItem
|
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.checkUpdate
|
||||||
import li.songe.gkd.util.launchTry
|
import li.songe.gkd.util.launchTry
|
||||||
import li.songe.gkd.util.logZipDir
|
import li.songe.gkd.util.logZipDir
|
||||||
|
import li.songe.gkd.util.map
|
||||||
import li.songe.gkd.util.newVersionApkDir
|
import li.songe.gkd.util.newVersionApkDir
|
||||||
import li.songe.gkd.util.snapshotZipDir
|
import li.songe.gkd.util.snapshotZipDir
|
||||||
import li.songe.gkd.util.storeFlow
|
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() {
|
override fun onCleared() {
|
||||||
super.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 groupToCategoryMap by lazy {
|
||||||
val map = mutableMapOf<RawAppGroup, RawCategory>()
|
val map = mutableMapOf<RawAppGroup, RawCategory>()
|
||||||
categoryToGroupsMap.forEach { (key, value) ->
|
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.useSafeGetTasksFc
|
||||||
import li.songe.gkd.shizuku.useSafeInputTapFc
|
import li.songe.gkd.shizuku.useSafeInputTapFc
|
||||||
import li.songe.gkd.shizuku.useShizukuAliveState
|
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.VOLUME_CHANGED_ACTION
|
||||||
import li.songe.gkd.util.checkSubsUpdate
|
import li.songe.gkd.util.checkSubsUpdate
|
||||||
import li.songe.gkd.util.launchTry
|
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.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||||
import li.songe.gkd.BuildConfig
|
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.GIT_COMMIT_URL
|
||||||
import li.songe.gkd.util.LocalNavController
|
import li.songe.gkd.util.LocalNavController
|
||||||
import li.songe.gkd.util.ProfileTransitions
|
import li.songe.gkd.util.ProfileTransitions
|
||||||
|
@ -71,7 +72,7 @@ fun AboutPage() {
|
||||||
context.openUri(REPOSITORY_URL)
|
context.openUri(REPOSITORY_URL)
|
||||||
}
|
}
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp, 12.dp)
|
.itemPadding()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "开源地址",
|
text = "开源地址",
|
||||||
|
@ -86,7 +87,7 @@ fun AboutPage() {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp, 12.dp)
|
.itemPadding()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "版本代码",
|
text = "版本代码",
|
||||||
|
@ -101,7 +102,7 @@ fun AboutPage() {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp, 12.dp)
|
.itemPadding()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "版本名称",
|
text = "版本名称",
|
||||||
|
@ -120,7 +121,7 @@ fun AboutPage() {
|
||||||
context.openUri(GIT_COMMIT_URL)
|
context.openUri(GIT_COMMIT_URL)
|
||||||
}
|
}
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp, 12.dp)
|
.itemPadding()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "代码记录",
|
text = "代码记录",
|
||||||
|
@ -136,7 +137,7 @@ fun AboutPage() {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp, 12.dp)
|
.itemPadding()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "构建时间",
|
text = "构建时间",
|
||||||
|
@ -151,7 +152,7 @@ fun AboutPage() {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp, 12.dp)
|
.itemPadding()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
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.SettingItem
|
||||||
import li.songe.gkd.ui.component.TextSwitch
|
import li.songe.gkd.ui.component.TextSwitch
|
||||||
import li.songe.gkd.ui.destinations.SnapshotPageDestination
|
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.LocalLauncher
|
||||||
import li.songe.gkd.util.LocalNavController
|
import li.songe.gkd.util.LocalNavController
|
||||||
import li.songe.gkd.util.ProfileTransitions
|
import li.songe.gkd.util.ProfileTransitions
|
||||||
|
@ -124,7 +125,7 @@ fun AdvancedPage() {
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Shizuku",
|
text = "Shizuku",
|
||||||
modifier = Modifier.padding(16.dp, 12.dp),
|
modifier = Modifier.itemPadding(),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
|
@ -150,12 +151,12 @@ fun AdvancedPage() {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "HTTP服务",
|
text = "HTTP服务",
|
||||||
modifier = Modifier.padding(16.dp, 12.dp),
|
modifier = Modifier.itemPadding(),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(16.dp, 14.dp),
|
modifier = Modifier.itemPadding(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
@ -215,7 +216,7 @@ fun AdvancedPage() {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable { showPortDlg = true }
|
.clickable { showPortDlg = true }
|
||||||
.padding(16.dp, 12.dp),
|
.itemPadding(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
@ -245,7 +246,7 @@ fun AdvancedPage() {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "快照",
|
text = "快照",
|
||||||
modifier = Modifier.padding(16.dp, 12.dp),
|
modifier = Modifier.itemPadding(),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
|
@ -335,7 +336,7 @@ fun AdvancedPage() {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "其它",
|
text = "其它",
|
||||||
modifier = Modifier.padding(16.dp, 12.dp),
|
modifier = Modifier.itemPadding(),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,7 +25,6 @@ import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Scaffold
|
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.db.DbSet
|
||||||
import li.songe.gkd.ui.destinations.AppItemPageDestination
|
import li.songe.gkd.ui.destinations.AppItemPageDestination
|
||||||
import li.songe.gkd.ui.destinations.GlobalRulePageDestination
|
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.LocalNavController
|
||||||
import li.songe.gkd.util.ProfileTransitions
|
import li.songe.gkd.util.ProfileTransitions
|
||||||
|
import li.songe.gkd.util.RuleSortOption
|
||||||
import li.songe.gkd.util.appInfoCacheFlow
|
import li.songe.gkd.util.appInfoCacheFlow
|
||||||
import li.songe.gkd.util.launchTry
|
import li.songe.gkd.util.launchTry
|
||||||
import li.songe.gkd.util.navigate
|
import li.songe.gkd.util.navigate
|
||||||
|
@ -80,7 +81,6 @@ fun AppConfigPage(appId: String) {
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
var isFirstVisit by remember { mutableStateOf(true) }
|
var isFirstVisit by remember { mutableStateOf(true) }
|
||||||
globalGroups.map { g -> g.group }
|
|
||||||
LaunchedEffect(globalGroups.size, appGroups.size, ruleSortType.value) {
|
LaunchedEffect(globalGroups.size, appGroups.size, ruleSortType.value) {
|
||||||
if (isFirstVisit) {
|
if (isFirstVisit) {
|
||||||
isFirstVisit = false
|
isFirstVisit = false
|
||||||
|
@ -124,7 +124,7 @@ fun AppConfigPage(appId: String) {
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
onDismissRequest = { expanded = false }
|
onDismissRequest = { expanded = false }
|
||||||
) {
|
) {
|
||||||
RuleSortType.allSubObject.forEach { s ->
|
RuleSortOption.allSubObject.forEach { s ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = {
|
text = {
|
||||||
Row(
|
Row(
|
||||||
|
@ -250,7 +250,7 @@ private fun AppGroupCard(
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(10.dp, 6.dp),
|
.itemPadding(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
@ -265,7 +265,8 @@ private fun AppGroupCard(
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
)
|
)
|
||||||
if (group.valid) {
|
if (group.valid) {
|
||||||
if (!group.desc.isNullOrBlank()) {
|
if (!group.desc.isNullOrBlank()) {
|
||||||
|
@ -275,14 +276,15 @@ private fun AppGroupCard(
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "暂无描述",
|
text = "暂无描述",
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = LocalContentColor.current.copy(alpha = 0.5f)
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.stateIn
|
||||||
import li.songe.gkd.data.SubsConfig
|
import li.songe.gkd.data.SubsConfig
|
||||||
import li.songe.gkd.db.DbSet
|
import li.songe.gkd.db.DbSet
|
||||||
import li.songe.gkd.ui.destinations.AppConfigPageDestination
|
import li.songe.gkd.ui.destinations.AppConfigPageDestination
|
||||||
|
import li.songe.gkd.util.RuleSortOption
|
||||||
import li.songe.gkd.util.collator
|
import li.songe.gkd.util.collator
|
||||||
import li.songe.gkd.util.ruleSummaryFlow
|
import li.songe.gkd.util.ruleSummaryFlow
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -29,7 +30,7 @@ class AppConfigVm @Inject constructor(stateHandle: SavedStateHandle) : ViewModel
|
||||||
SubsConfig.AppGroupType
|
SubsConfig.AppGroupType
|
||||||
)
|
)
|
||||||
|
|
||||||
val ruleSortTypeFlow = MutableStateFlow<RuleSortType>(RuleSortType.Default)
|
val ruleSortTypeFlow = MutableStateFlow<RuleSortOption>(RuleSortOption.Default)
|
||||||
|
|
||||||
val globalGroupsFlow = combine(
|
val globalGroupsFlow = combine(
|
||||||
ruleSummaryFlow.map { r -> r.globalGroups },
|
ruleSummaryFlow.map { r -> r.globalGroups },
|
||||||
|
@ -37,15 +38,15 @@ class AppConfigVm @Inject constructor(stateHandle: SavedStateHandle) : ViewModel
|
||||||
latestGlobalLogsFlow
|
latestGlobalLogsFlow
|
||||||
) { list, type, logs ->
|
) { list, type, logs ->
|
||||||
when (type) {
|
when (type) {
|
||||||
RuleSortType.Default -> list
|
RuleSortOption.Default -> list
|
||||||
RuleSortType.ByName -> list.sortedWith { a, b ->
|
RuleSortOption.ByName -> list.sortedWith { a, b ->
|
||||||
collator.compare(
|
collator.compare(
|
||||||
a.group.name,
|
a.group.name,
|
||||||
b.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
|
-(logs.find { c -> c.groupKey == a.group.key && c.subsId == a.subsItem.id }?.id
|
||||||
?: 0)
|
?: 0)
|
||||||
}
|
}
|
||||||
|
@ -58,15 +59,15 @@ class AppConfigVm @Inject constructor(stateHandle: SavedStateHandle) : ViewModel
|
||||||
latestAppLogsFlow
|
latestAppLogsFlow
|
||||||
) { list, type, logs ->
|
) { list, type, logs ->
|
||||||
when (type) {
|
when (type) {
|
||||||
RuleSortType.Default -> list
|
RuleSortOption.Default -> list
|
||||||
RuleSortType.ByName -> list.sortedWith { a, b ->
|
RuleSortOption.ByName -> list.sortedWith { a, b ->
|
||||||
collator.compare(
|
collator.compare(
|
||||||
a.group.name,
|
a.group.name,
|
||||||
b.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
|
-(logs.find { c -> c.groupKey == a.group.key && c.subsId == a.subsItem.id }?.id
|
||||||
?: 0)
|
?: 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.FloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
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.db.DbSet
|
||||||
import li.songe.gkd.ui.component.getDialogResult
|
import li.songe.gkd.ui.component.getDialogResult
|
||||||
import li.songe.gkd.ui.destinations.GroupItemPageDestination
|
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.LocalNavController
|
||||||
import li.songe.gkd.util.ProfileTransitions
|
import li.songe.gkd.util.ProfileTransitions
|
||||||
import li.songe.gkd.util.appInfoCacheFlow
|
import li.songe.gkd.util.appInfoCacheFlow
|
||||||
|
@ -166,7 +166,7 @@ fun AppItemPage(
|
||||||
if (group.key == focusGroupKey) MaterialTheme.colorScheme.inversePrimary else Color.Transparent
|
if (group.key == focusGroupKey) MaterialTheme.colorScheme.inversePrimary else Color.Transparent
|
||||||
)
|
)
|
||||||
.clickable { setShowGroupItem(group) }
|
.clickable { setShowGroupItem(group) }
|
||||||
.padding(10.dp, 6.dp),
|
.itemPadding(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
@ -181,7 +181,8 @@ fun AppItemPage(
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
)
|
)
|
||||||
if (group.valid) {
|
if (group.valid) {
|
||||||
if (!group.desc.isNullOrBlank()) {
|
if (!group.desc.isNullOrBlank()) {
|
||||||
|
@ -191,21 +192,22 @@ fun AppItemPage(
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
fontSize = 14.sp
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "暂无描述",
|
text = "暂无描述",
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
fontSize = 14.sp,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = LocalContentColor.current.copy(alpha = 0.5f)
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "非法选择器",
|
text = "非法选择器",
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
fontSize = 14.sp,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.error
|
color = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -343,13 +345,18 @@ fun AppItemPage(
|
||||||
}
|
}
|
||||||
|
|
||||||
showGroupItem?.let { showGroupItemVal ->
|
showGroupItem?.let { showGroupItemVal ->
|
||||||
AlertDialog(modifier = Modifier.defaultMinSize(300.dp),
|
AlertDialog(
|
||||||
|
modifier = Modifier.defaultMinSize(300.dp),
|
||||||
onDismissRequest = { setShowGroupItem(null) },
|
onDismissRequest = { setShowGroupItem(null) },
|
||||||
title = {
|
title = {
|
||||||
Text(text = showGroupItemVal.name)
|
Text(text = "规则组详情")
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = showGroupItemVal.desc ?: "")
|
Column {
|
||||||
|
Text(text = showGroupItemVal.name)
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
Text(text = showGroupItemVal.desc ?: "")
|
||||||
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
if (showGroupItemVal.allExampleUrls.isNotEmpty()) {
|
if (showGroupItemVal.allExampleUrls.isNotEmpty()) {
|
||||||
|
|
|
@ -12,25 +12,19 @@ import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
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.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
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.Add
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
import androidx.compose.material.icons.filled.UnfoldMore
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.RadioButton
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
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.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
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.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
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.data.RawSubscription
|
||||||
import li.songe.gkd.db.DbSet
|
import li.songe.gkd.db.DbSet
|
||||||
import li.songe.gkd.ui.component.getDialogResult
|
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.LocalNavController
|
||||||
import li.songe.gkd.util.ProfileTransitions
|
import li.songe.gkd.util.ProfileTransitions
|
||||||
|
import li.songe.gkd.util.findOption
|
||||||
import li.songe.gkd.util.launchTry
|
import li.songe.gkd.util.launchTry
|
||||||
import li.songe.gkd.util.toast
|
import li.songe.gkd.util.toast
|
||||||
import li.songe.gkd.util.updateSubscription
|
import li.songe.gkd.util.updateSubscription
|
||||||
|
|
||||||
val enableGroupRadioOptions = arrayOf(
|
|
||||||
"跟随订阅" to null,
|
|
||||||
"全部启用" to true,
|
|
||||||
"全部关闭" to false,
|
|
||||||
)
|
|
||||||
|
|
||||||
@RootNavGraph
|
@RootNavGraph
|
||||||
@Destination(style = ProfileTransitions::class)
|
@Destination(style = ProfileTransitions::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -84,15 +73,13 @@ fun CategoryPage(subsItemId: Long) {
|
||||||
var showAddDlg by remember {
|
var showAddDlg by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
var editEnableCategory by remember {
|
|
||||||
mutableStateOf<RawSubscription.RawCategory?>(null)
|
|
||||||
}
|
|
||||||
val (editNameCategory, setEditNameCategory) = remember {
|
val (editNameCategory, setEditNameCategory) = remember {
|
||||||
mutableStateOf<RawSubscription.RawCategory?>(null)
|
mutableStateOf<RawSubscription.RawCategory?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val categories = subsRaw?.categories ?: emptyList()
|
val categories = subsRaw?.categories ?: emptyList()
|
||||||
val categoriesGroups = subsRaw?.categoryToGroupsMap ?: emptyMap()
|
val categoriesGroups = subsRaw?.categoryToGroupsMap ?: emptyMap()
|
||||||
|
val categoriesApps = subsRaw?.categoryToAppMap ?: emptyMap()
|
||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||||
Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = {
|
Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = {
|
||||||
|
@ -120,35 +107,36 @@ fun CategoryPage(subsItemId: Long) {
|
||||||
modifier = Modifier.padding(contentPadding)
|
modifier = Modifier.padding(contentPadding)
|
||||||
) {
|
) {
|
||||||
items(categories, { it.key }) { category ->
|
items(categories, { it.key }) { category ->
|
||||||
|
var selectedExpanded by remember { mutableStateOf(false) }
|
||||||
Row(modifier = Modifier
|
Row(modifier = Modifier
|
||||||
.clickable {
|
.clickable { selectedExpanded = true }
|
||||||
editEnableCategory = category
|
.itemPadding(),
|
||||||
}
|
verticalAlignment = Alignment.CenterVertically) {
|
||||||
.padding(10.dp, 6.dp), verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
val size = categoriesGroups[category]?.size ?: 0
|
val size = categoriesGroups[category]?.size ?: 0
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = category.name, fontSize = 18.sp
|
text = category.name,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
)
|
)
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
|
val appSize = categoriesApps[category]?.size ?: 0
|
||||||
Text(
|
Text(
|
||||||
text = "${size}规则组",
|
text = "${appSize}应用/${size}规则组",
|
||||||
fontSize = 14.sp
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "暂无规则",
|
text = "暂无规则",
|
||||||
fontSize = 14.sp,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = LocalContentColor.current.copy(alpha = 0.5f)
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (editable) {
|
if (editable) {
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier.wrapContentSize(Alignment.TopStart)
|
||||||
.wrapContentSize(Alignment.TopStart)
|
|
||||||
) {
|
) {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
expanded = true
|
expanded = true
|
||||||
|
@ -158,45 +146,33 @@ fun CategoryPage(subsItemId: Long) {
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DropdownMenu(
|
DropdownMenu(expanded = expanded,
|
||||||
expanded = expanded,
|
onDismissRequest = { expanded = false }) {
|
||||||
onDismissRequest = { expanded = false }
|
DropdownMenuItem(text = {
|
||||||
) {
|
Text(text = "编辑")
|
||||||
DropdownMenuItem(
|
}, onClick = {
|
||||||
text = {
|
expanded = false
|
||||||
Text(text = "编辑")
|
setEditNameCategory(category)
|
||||||
},
|
})
|
||||||
onClick = {
|
DropdownMenuItem(text = {
|
||||||
expanded = false
|
Text(text = "删除", color = MaterialTheme.colorScheme.error)
|
||||||
setEditNameCategory(category)
|
}, onClick = {
|
||||||
}
|
expanded = false
|
||||||
)
|
vm.viewModelScope.launchTry {
|
||||||
DropdownMenuItem(
|
val result = getDialogResult(
|
||||||
text = {
|
"删除类别", "是否删除类别 ${category.name} ?"
|
||||||
Text(text = "删除", color = MaterialTheme.colorScheme.error)
|
)
|
||||||
},
|
if (!result) return@launchTry
|
||||||
onClick = {
|
subsItem?.apply {
|
||||||
expanded = false
|
updateSubscription(subsRaw!!.copy(categories = subsRaw!!.categories.filter { c -> c.key != category.key }))
|
||||||
vm.viewModelScope.launchTry {
|
DbSet.subsItemDao.update(copy(mtime = System.currentTimeMillis()))
|
||||||
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("删除成功")
|
|
||||||
}
|
}
|
||||||
|
DbSet.categoryConfigDao.deleteByCategoryKey(
|
||||||
|
subsItemId, category.key
|
||||||
|
)
|
||||||
|
toast("删除成功")
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,19 +186,38 @@ fun CategoryPage(subsItemId: Long) {
|
||||||
val enable =
|
val enable =
|
||||||
if (categoryConfig != null) categoryConfig.enable else category.enable
|
if (categoryConfig != null) categoryConfig.enable else category.enable
|
||||||
Text(
|
Text(
|
||||||
text = enableGroupRadioOptions.find { e -> e.second == enable }?.first
|
text = EnableGroupOption.allSubObject.findOption(enable).label,
|
||||||
?: "",
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
fontSize = 14.sp
|
|
||||||
)
|
)
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
imageVector = Icons.Default.UnfoldMore, contentDescription = null
|
||||||
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 {
|
item {
|
||||||
Spacer(modifier = Modifier.height(40.dp))
|
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
|
val subsRawVal = subsRaw
|
||||||
if (editNameCategory != null && subsRawVal != null) {
|
if (editNameCategory != null && subsRawVal != null) {
|
||||||
var source by remember {
|
var source by remember {
|
||||||
mutableStateOf(editNameCategory.name)
|
mutableStateOf(editNameCategory.name)
|
||||||
}
|
}
|
||||||
AlertDialog(
|
AlertDialog(title = { Text(text = "编辑类别") }, text = {
|
||||||
title = { Text(text = "编辑类别") },
|
OutlinedTextField(
|
||||||
text = {
|
value = source,
|
||||||
OutlinedTextField(
|
onValueChange = { source = it.trim() },
|
||||||
value = source,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onValueChange = { source = it.trim() },
|
placeholder = { Text(text = "请输入类别名称") },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
singleLine = true
|
||||||
placeholder = { Text(text = "请输入类别名称") },
|
)
|
||||||
singleLine = true
|
}, onDismissRequest = { setEditNameCategory(null) }, dismissButton = {
|
||||||
)
|
TextButton(onClick = { setEditNameCategory(null) }) {
|
||||||
},
|
Text(text = "取消")
|
||||||
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 = "确认")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}, 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) {
|
if (showAddDlg && subsRawVal != null) {
|
||||||
var source by remember {
|
var source by remember {
|
||||||
mutableStateOf("")
|
mutableStateOf("")
|
||||||
}
|
}
|
||||||
AlertDialog(
|
AlertDialog(title = { Text(text = "添加类别") }, text = {
|
||||||
title = { Text(text = "添加类别") },
|
OutlinedTextField(
|
||||||
text = {
|
value = source,
|
||||||
OutlinedTextField(
|
onValueChange = { source = it.trim() },
|
||||||
value = source,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onValueChange = { source = it.trim() },
|
placeholder = { Text(text = "请输入类别名称") },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
singleLine = true
|
||||||
placeholder = { Text(text = "请输入类别名称") },
|
)
|
||||||
singleLine = true
|
}, onDismissRequest = { showAddDlg = false }, dismissButton = {
|
||||||
)
|
TextButton(onClick = { showAddDlg = false }) {
|
||||||
},
|
Text(text = "取消")
|
||||||
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 = "确认")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}, 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.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Scaffold
|
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.db.DbSet
|
||||||
import li.songe.gkd.service.launcherAppId
|
import li.songe.gkd.service.launcherAppId
|
||||||
import li.songe.gkd.ui.component.AppBarTextField
|
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.LocalNavController
|
||||||
import li.songe.gkd.util.ProfileTransitions
|
import li.songe.gkd.util.ProfileTransitions
|
||||||
import li.songe.gkd.util.SortTypeOption
|
import li.songe.gkd.util.SortTypeOption
|
||||||
|
@ -239,7 +240,7 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(IntrinsicSize.Min)
|
.height(IntrinsicSize.Min)
|
||||||
.padding(10.dp, 6.dp),
|
.itemPadding(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
@ -283,7 +284,7 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) {
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
style = LocalTextStyle.current.let {
|
style = MaterialTheme.typography.bodyLarge.let {
|
||||||
if (appInfo.isSystem) {
|
if (appInfo.isSystem) {
|
||||||
it.copy(textDecoration = TextDecoration.Underline)
|
it.copy(textDecoration = TextDecoration.Underline)
|
||||||
} else {
|
} else {
|
||||||
|
@ -296,7 +297,9 @@ fun GlobalRuleExcludePage(subsItemId: Long, groupKey: Int) {
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis,
|
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.FloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
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.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
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.component.getDialogResult
|
||||||
import li.songe.gkd.ui.destinations.GlobalRuleExcludePageDestination
|
import li.songe.gkd.ui.destinations.GlobalRuleExcludePageDestination
|
||||||
import li.songe.gkd.ui.destinations.GroupItemPageDestination
|
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.LocalNavController
|
||||||
import li.songe.gkd.util.ProfileTransitions
|
import li.songe.gkd.util.ProfileTransitions
|
||||||
import li.songe.gkd.util.encodeToJson5String
|
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
|
if (group.key == focusGroupKey) MaterialTheme.colorScheme.inversePrimary else Color.Transparent
|
||||||
)
|
)
|
||||||
.clickable { setShowGroupItem(group) }
|
.clickable { setShowGroupItem(group) }
|
||||||
.padding(10.dp, 6.dp),
|
.itemPadding(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
@ -154,7 +153,8 @@ fun GlobalRulePage(subsItemId: Long, focusGroupKey: Int? = null) {
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
)
|
)
|
||||||
if (group.valid) {
|
if (group.valid) {
|
||||||
if (!group.desc.isNullOrBlank()) {
|
if (!group.desc.isNullOrBlank()) {
|
||||||
|
@ -164,21 +164,22 @@ fun GlobalRulePage(subsItemId: Long, focusGroupKey: Int? = null) {
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
fontSize = 14.sp
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "暂无描述",
|
text = "暂无描述",
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
fontSize = 14.sp,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = LocalContentColor.current.copy(alpha = 0.5f)
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "非法选择器",
|
text = "非法选择器",
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
fontSize = 14.sp,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.error
|
color = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -449,10 +450,14 @@ fun GlobalRulePage(subsItemId: Long, focusGroupKey: Int? = null) {
|
||||||
modifier = Modifier.defaultMinSize(300.dp),
|
modifier = Modifier.defaultMinSize(300.dp),
|
||||||
onDismissRequest = { setShowGroupItem(null) },
|
onDismissRequest = { setShowGroupItem(null) },
|
||||||
title = {
|
title = {
|
||||||
Text(text = showGroupItem.name)
|
Text(text = "规则组详情")
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = showGroupItem.desc ?: "")
|
Column {
|
||||||
|
Text(text = showGroupItem.name)
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
Text(text = showGroupItem.desc ?: "")
|
||||||
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
if (showGroupItem.allExampleUrls.isNotEmpty()) {
|
if (showGroupItem.allExampleUrls.isNotEmpty()) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
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.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||||
import li.songe.gkd.ui.destinations.AppItemPageDestination
|
import li.songe.gkd.ui.destinations.AppItemPageDestination
|
||||||
import li.songe.gkd.ui.destinations.GlobalRulePageDestination
|
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.LocalNavController
|
||||||
import li.songe.gkd.util.ProfileTransitions
|
import li.songe.gkd.util.ProfileTransitions
|
||||||
import li.songe.gkd.util.appInfoCacheFlow
|
import li.songe.gkd.util.appInfoCacheFlow
|
||||||
|
@ -98,7 +99,7 @@ fun SlowGroupPage() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding(10.dp, 5.dp),
|
.itemPadding(),
|
||||||
title = group.name,
|
title = group.name,
|
||||||
desc = "${rule.rawSubs.name}/全局规则"
|
desc = "${rule.rawSubs.name}/全局规则"
|
||||||
)
|
)
|
||||||
|
@ -118,12 +119,12 @@ fun SlowGroupPage() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding(10.dp, 5.dp),
|
.itemPadding(),
|
||||||
title = group.name,
|
title = group.name,
|
||||||
desc = "${rule.rawSubs.name}/应用规则/${appInfoCache[rule.app.id]?.name ?: rule.app.name ?: rule.app.id}"
|
desc = "${rule.rawSubs.name}/应用规则/${appInfoCache[rule.app.id]?.name ?: rule.app.name ?: rule.app.id}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item("empty") {
|
item {
|
||||||
Spacer(modifier = Modifier.height(40.dp))
|
Spacer(modifier = Modifier.height(40.dp))
|
||||||
if (ruleSummary.slowGroupCount == 0) {
|
if (ruleSummary.slowGroupCount == 0) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -160,17 +161,18 @@ fun SlowGroupCard(title: String, desc: String, modifier: Modifier = Modifier) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
fontSize = 18.sp,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = desc,
|
text = desc,
|
||||||
fontSize = 14.sp,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Icon(
|
Icon(
|
||||||
|
|
|
@ -259,19 +259,17 @@ fun SubsPage(
|
||||||
DbSet.subsConfigDao.insert(newItem)
|
DbSet.subsConfigDao.insert(newItem)
|
||||||
},
|
},
|
||||||
showMenu = editable,
|
showMenu = editable,
|
||||||
onDelClick = {
|
onDelClick = vm.viewModelScope.launchAsFn {
|
||||||
vm.viewModelScope.launchTry {
|
val result = getDialogResult(
|
||||||
val result = getDialogResult(
|
"删除规则组",
|
||||||
"删除规则组",
|
"确定删除 ${appInfoCache[appRaw.id]?.name ?: appRaw.name ?: appRaw.id} 下所有规则组?"
|
||||||
"确定删除 ${appInfoCache[appRaw.id]?.name ?: appRaw.name ?: appRaw.id} 下所有规则组?"
|
)
|
||||||
)
|
if (!result) return@launchAsFn
|
||||||
if (!result) return@launchTry
|
if (subsRaw != null && subsItem != null) {
|
||||||
if (subsRaw != null && subsItem != null) {
|
updateSubscription(subsRaw.copy(apps = subsRaw.apps.filter { a -> a.id != appRaw.id }))
|
||||||
updateSubscription(subsRaw.copy(apps = subsRaw.apps.filter { a -> a.id != appRaw.id }))
|
DbSet.subsItemDao.update(subsItem.copy(mtime = System.currentTimeMillis()))
|
||||||
DbSet.subsItemDao.update(subsItem.copy(mtime = System.currentTimeMillis()))
|
DbSet.subsConfigDao.delete(subsItem.id, appRaw.id)
|
||||||
DbSet.subsConfigDao.delete(subsItem.id, appRaw.id)
|
toast("删除成功")
|
||||||
toast("删除成功")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package li.songe.gkd.ui.component
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
@ -12,6 +11,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import li.songe.gkd.ui.style.itemPadding
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AuthCard(
|
fun AuthCard(
|
||||||
|
@ -20,7 +20,7 @@ fun AuthCard(
|
||||||
onAuthClick: () -> Unit,
|
onAuthClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(16.dp, 12.dp),
|
modifier = Modifier.itemPadding(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
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.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
@ -14,7 +13,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.unit.dp
|
import li.songe.gkd.ui.style.itemPadding
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingItem(
|
fun SettingItem(
|
||||||
|
@ -28,7 +27,7 @@ fun SettingItem(
|
||||||
onClick = onClick
|
onClick = onClick
|
||||||
)
|
)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp, 12.dp),
|
.itemPadding(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -22,8 +22,6 @@ import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
|
||||||
import androidx.compose.material3.LocalTextStyle
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
@ -109,7 +107,7 @@ fun SubsAppCard(
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
style = LocalTextStyle.current.let {
|
style = MaterialTheme.typography.bodyLarge.let {
|
||||||
if (appInfo?.isSystem == true) {
|
if (appInfo?.isSystem == true) {
|
||||||
it.copy(textDecoration = TextDecoration.Underline)
|
it.copy(textDecoration = TextDecoration.Underline)
|
||||||
} else {
|
} else {
|
||||||
|
@ -129,13 +127,16 @@ fun SubsAppCard(
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "暂无规则",
|
text = "暂无规则",
|
||||||
modifier = Modifier.fillMaxWidth(),
|
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.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
|
@ -12,30 +11,38 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import li.songe.gkd.ui.style.itemPadding
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TextSwitch(
|
fun TextSwitch(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
name: String = "",
|
name: String,
|
||||||
desc: String = "",
|
desc: String? = null,
|
||||||
checked: Boolean = true,
|
checked: Boolean = true,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
onCheckedChange: ((Boolean) -> Unit)? = null,
|
onCheckedChange: ((Boolean) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.padding(16.dp, 12.dp),
|
modifier = modifier.itemPadding(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
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(
|
Text(
|
||||||
name,
|
text = name,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
)
|
)
|
||||||
Text(
|
|
||||||
desc,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
Switch(
|
Switch(
|
||||||
|
|
|
@ -42,6 +42,7 @@ import li.songe.gkd.ui.component.AuthCard
|
||||||
import li.songe.gkd.ui.component.TextSwitch
|
import li.songe.gkd.ui.component.TextSwitch
|
||||||
import li.songe.gkd.ui.destinations.ClickLogPageDestination
|
import li.songe.gkd.ui.destinations.ClickLogPageDestination
|
||||||
import li.songe.gkd.ui.destinations.SlowGroupPageDestination
|
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.HOME_PAGE_URL
|
||||||
import li.songe.gkd.util.LocalNavController
|
import li.songe.gkd.util.LocalNavController
|
||||||
import li.songe.gkd.util.launchAsFn
|
import li.songe.gkd.util.launchAsFn
|
||||||
|
@ -131,7 +132,7 @@ fun useControlPage(): ScaffoldExt {
|
||||||
.clickable {
|
.clickable {
|
||||||
context.openUri(HOME_PAGE_URL)
|
context.openUri(HOME_PAGE_URL)
|
||||||
}
|
}
|
||||||
.padding(16.dp, 12.dp),
|
.itemPadding(),
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -158,7 +159,7 @@ fun useControlPage(): ScaffoldExt {
|
||||||
.clickable {
|
.clickable {
|
||||||
navController.navigate(ClickLogPageDestination)
|
navController.navigate(ClickLogPageDestination)
|
||||||
}
|
}
|
||||||
.padding(16.dp, 12.dp),
|
.itemPadding(),
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -185,7 +186,7 @@ fun useControlPage(): ScaffoldExt {
|
||||||
.clickable {
|
.clickable {
|
||||||
navController.navigate(SlowGroupPageDestination)
|
navController.navigate(SlowGroupPageDestination)
|
||||||
}
|
}
|
||||||
.padding(16.dp, 12.dp),
|
.itemPadding(),
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -208,7 +209,7 @@ fun useControlPage(): ScaffoldExt {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp, 12.dp)
|
.itemPadding()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = subsStatus,
|
text = subsStatus,
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
package li.songe.gkd.ui.home
|
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.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
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.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.selection.selectable
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
import androidx.compose.material.icons.filled.Share
|
||||||
import androidx.compose.material.icons.filled.Autorenew
|
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.RadioButton
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
@ -41,7 +31,6 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
@ -56,22 +45,28 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import li.songe.gkd.MainActivity
|
import li.songe.gkd.MainActivity
|
||||||
import li.songe.gkd.appScope
|
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.SettingItem
|
||||||
|
import li.songe.gkd.ui.component.TextMenu
|
||||||
import li.songe.gkd.ui.component.TextSwitch
|
import li.songe.gkd.ui.component.TextSwitch
|
||||||
import li.songe.gkd.ui.destinations.AboutPageDestination
|
import li.songe.gkd.ui.destinations.AboutPageDestination
|
||||||
import li.songe.gkd.ui.destinations.AdvancedPageDestination
|
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.LoadStatus
|
||||||
import li.songe.gkd.util.LocalNavController
|
import li.songe.gkd.util.LocalNavController
|
||||||
|
import li.songe.gkd.util.UpdateTimeOption
|
||||||
import li.songe.gkd.util.buildLogFile
|
import li.songe.gkd.util.buildLogFile
|
||||||
import li.songe.gkd.util.checkUpdate
|
import li.songe.gkd.util.checkUpdate
|
||||||
import li.songe.gkd.util.checkUpdatingFlow
|
import li.songe.gkd.util.checkUpdatingFlow
|
||||||
|
import li.songe.gkd.util.findOption
|
||||||
import li.songe.gkd.util.launchAsFn
|
import li.songe.gkd.util.launchAsFn
|
||||||
import li.songe.gkd.util.launchTry
|
import li.songe.gkd.util.launchTry
|
||||||
import li.songe.gkd.util.navigate
|
import li.songe.gkd.util.navigate
|
||||||
import li.songe.gkd.util.shareFile
|
import li.songe.gkd.util.shareFile
|
||||||
import li.songe.gkd.util.storeFlow
|
import li.songe.gkd.util.storeFlow
|
||||||
import li.songe.gkd.util.toast
|
import li.songe.gkd.util.toast
|
||||||
import kotlin.math.sin
|
|
||||||
|
|
||||||
val settingsNav = BottomNavItem(
|
val settingsNav = BottomNavItem(
|
||||||
label = "设置", icon = Icons.Outlined.Settings
|
label = "设置", icon = Icons.Outlined.Settings
|
||||||
|
@ -85,12 +80,6 @@ fun useSettingsPage(): ScaffoldExt {
|
||||||
val vm = hiltViewModel<HomeVm>()
|
val vm = hiltViewModel<HomeVm>()
|
||||||
val uploadStatus by vm.uploadStatusFlow.collectAsState()
|
val uploadStatus by vm.uploadStatusFlow.collectAsState()
|
||||||
|
|
||||||
var showSubsIntervalDlg by remember {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
var showEnableDarkThemeDlg by remember {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
var showToastInputDlg by remember {
|
var showToastInputDlg by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
@ -101,74 +90,6 @@ fun useSettingsPage(): ScaffoldExt {
|
||||||
|
|
||||||
val checkUpdating by checkUpdatingFlow.collectAsState()
|
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) {
|
if (showToastInputDlg) {
|
||||||
var value by remember {
|
var value by remember {
|
||||||
mutableStateOf(store.clickToast)
|
mutableStateOf(store.clickToast)
|
||||||
|
@ -335,12 +256,13 @@ fun useSettingsPage(): ScaffoldExt {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "常规",
|
text = "常规",
|
||||||
modifier = Modifier.padding(16.dp, 12.dp),
|
modifier = Modifier.itemPadding(),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
|
|
||||||
TextSwitch(name = "触发提示",
|
TextSwitch(
|
||||||
|
name = "触发提示",
|
||||||
desc = store.clickToast,
|
desc = store.clickToast,
|
||||||
checked = store.toastWhenClick,
|
checked = store.toastWhenClick,
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
|
@ -350,83 +272,57 @@ fun useSettingsPage(): ScaffoldExt {
|
||||||
storeFlow.value = store.copy(
|
storeFlow.value = store.copy(
|
||||||
toastWhenClick = it
|
toastWhenClick = it
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
TextSwitch(
|
TextSwitch(
|
||||||
name = "后台隐藏",
|
name = "后台隐藏",
|
||||||
desc = "在[最近任务]界面中隐藏本应用",
|
desc = "在[最近任务]中隐藏本应用",
|
||||||
checked = store.excludeFromRecents,
|
checked = store.excludeFromRecents,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
storeFlow.value = store.copy(
|
storeFlow.value = store.copy(
|
||||||
excludeFromRecents = it
|
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(
|
||||||
text = "更新",
|
text = "更新",
|
||||||
modifier = Modifier.padding(16.dp, 12.dp),
|
modifier = Modifier.itemPadding(),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(
|
TextMenu(
|
||||||
modifier = Modifier
|
title = "更新订阅",
|
||||||
.clickable {
|
option = UpdateTimeOption.allSubObject.findOption(store.updateSubsInterval)
|
||||||
showSubsIntervalDlg = true
|
|
||||||
}
|
|
||||||
.padding(16.dp, 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
Text(
|
storeFlow.update { s -> s.copy(updateSubsInterval = it.value) }
|
||||||
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"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSwitch(name = "自动更新应用",
|
TextSwitch(
|
||||||
desc = "打开应用时自动检测是否存在新版本",
|
name = "自动更新",
|
||||||
|
desc = "打开应用时检测新版本",
|
||||||
checked = store.autoCheckAppUpdate,
|
checked = store.autoCheckAppUpdate,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
storeFlow.value = store.copy(
|
storeFlow.value = store.copy(
|
||||||
|
@ -446,7 +342,7 @@ fun useSettingsPage(): ScaffoldExt {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp, 12.dp),
|
.itemPadding(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
@ -459,13 +355,14 @@ fun useSettingsPage(): ScaffoldExt {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "日志",
|
text = "日志",
|
||||||
modifier = Modifier.padding(16.dp, 12.dp),
|
modifier = Modifier.itemPadding(),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
|
|
||||||
TextSwitch(name = "保存日志",
|
TextSwitch(
|
||||||
desc = "保存最近7天日志,关闭后无法定位解决错误",
|
name = "保存日志",
|
||||||
|
desc = "保存7天日志,帮助定位BUG",
|
||||||
checked = store.log2FileSwitch,
|
checked = store.log2FileSwitch,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
storeFlow.value = store.copy(
|
storeFlow.value = store.copy(
|
||||||
|
@ -485,13 +382,17 @@ fun useSettingsPage(): ScaffoldExt {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
SettingItem(title = "分享日志", onClick = {
|
SettingItem(
|
||||||
showShareLogDlg = true
|
title = "分享日志",
|
||||||
})
|
imageVector = Icons.Default.Share,
|
||||||
|
onClick = {
|
||||||
|
showShareLogDlg = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "其它",
|
text = "其它",
|
||||||
modifier = Modifier.padding(16.dp, 12.dp),
|
modifier = Modifier.itemPadding(),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
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
|
package li.songe.gkd.ui.theme
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
@ -12,33 +11,33 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import li.songe.gkd.util.map
|
import li.songe.gkd.MainActivity
|
||||||
import li.songe.gkd.util.storeFlow
|
|
||||||
import li.songe.gkd.util.updateToastStyle
|
import li.songe.gkd.util.updateToastStyle
|
||||||
|
|
||||||
val LightColorScheme = lightColorScheme()
|
val LightColorScheme = lightColorScheme()
|
||||||
val DarkColorScheme = darkColorScheme()
|
val DarkColorScheme = darkColorScheme()
|
||||||
|
val supportDynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppTheme(
|
fun AppTheme(
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
// https://developer.android.com/jetpack/compose/designsystems/material3?hl=zh-cn
|
// https://developer.android.com/jetpack/compose/designsystems/material3?hl=zh-cn
|
||||||
val context = LocalContext.current as ComponentActivity
|
val context = LocalContext.current as MainActivity
|
||||||
val scope = rememberCoroutineScope()
|
val enableDarkTheme by context.mainVm.enableDarkThemeFlow.collectAsState()
|
||||||
val enableDarkTheme by storeFlow.map(scope) { s -> s.enableDarkTheme }.collectAsState()
|
val enableDynamicColor by context.mainVm.enableDynamicColorFlow.collectAsState()
|
||||||
val systemInDarkTheme = isSystemInDarkTheme()
|
val systemInDarkTheme = isSystemInDarkTheme()
|
||||||
val darkTheme = enableDarkTheme ?: systemInDarkTheme
|
val darkTheme = enableDarkTheme ?: systemInDarkTheme
|
||||||
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
|
||||||
val colorScheme = when {
|
val colorScheme = when {
|
||||||
dynamicColor && darkTheme -> dynamicDarkColorScheme(context)
|
supportDynamicColor && enableDynamicColor && darkTheme -> dynamicDarkColorScheme(context)
|
||||||
dynamicColor && !darkTheme -> dynamicLightColorScheme(context)
|
supportDynamicColor && enableDynamicColor && !darkTheme -> dynamicLightColorScheme(context)
|
||||||
darkTheme -> DarkColorScheme
|
darkTheme -> DarkColorScheme
|
||||||
else -> LightColorScheme
|
else -> LightColorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/gkd-kit/gkd/pull/421
|
||||||
LaunchedEffect(darkTheme) {
|
LaunchedEffect(darkTheme) {
|
||||||
WindowInsetsControllerCompat(context.window, context.window.decorView).apply {
|
WindowInsetsControllerCompat(context.window, context.window.decorView).apply {
|
||||||
isAppearanceLightStatusBars = !darkTheme
|
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.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import li.songe.gkd.appScope
|
import li.songe.gkd.appScope
|
||||||
import li.songe.gkd.ui.home.UpdateTimeOption
|
|
||||||
|
|
||||||
private inline fun <reified T> createStorageFlow(
|
private inline fun <reified T> createStorageFlow(
|
||||||
key: String,
|
key: String,
|
||||||
|
@ -57,6 +56,7 @@ data class Store(
|
||||||
val enableShizukuClick: Boolean = false,
|
val enableShizukuClick: Boolean = false,
|
||||||
val log2FileSwitch: Boolean = true,
|
val log2FileSwitch: Boolean = true,
|
||||||
val enableDarkTheme: Boolean? = null,
|
val enableDarkTheme: Boolean? = null,
|
||||||
|
val enableDynamicColor: Boolean = true,
|
||||||
val enableAbFloatWindow: Boolean = true,
|
val enableAbFloatWindow: Boolean = true,
|
||||||
val sortType: Int = SortTypeOption.SortByName.value,
|
val sortType: Int = SortTypeOption.SortByName.value,
|
||||||
val showSystemApp: Boolean = true,
|
val showSystemApp: Boolean = true,
|
||||||
|
@ -88,8 +88,6 @@ val clickCountFlow by lazy {
|
||||||
recordStoreFlow.map(appScope) { r -> r.clickCount }
|
recordStoreFlow.map(appScope) { r -> r.clickCount }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val log2FileSwitchFlow by lazy { storeFlow.map(appScope) { s -> s.log2FileSwitch } }
|
|
||||||
|
|
||||||
fun increaseClickCount(n: Int = 1) {
|
fun increaseClickCount(n: Int = 1) {
|
||||||
recordStoreFlow.update {
|
recordStoreFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
@ -101,10 +99,5 @@ fun increaseClickCount(n: Int = 1) {
|
||||||
fun initStore() {
|
fun initStore() {
|
||||||
storeFlow.value
|
storeFlow.value
|
||||||
recordStoreFlow.value
|
recordStoreFlow.value
|
||||||
appScope.launchTry(Dispatchers.IO) {
|
|
||||||
log2FileSwitchFlow.collect {
|
|
||||||
LogUtils.getConfig().isLog2FileSwitch = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user