From 5246c4b8595bcf96ae6c1abb30822b50b7eabf10 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 29 Apr 2026 16:07:45 +0800 Subject: [PATCH 1/4] Eliminate item nesting for preferences --- .../kr328/clash/crash/ui/ApkBrokenScreen.kt | 32 +- .../github/kr328/clash/main/ui/HelpScreen.kt | 77 +- .../clash/settings/ui/AppSettingsScreen.kt | 107 +- .../settings/ui/MetaFeatureSettingsScreen.kt | 567 ++++++----- .../settings/ui/NetworkSettingsScreen.kt | 189 ++-- .../settings/ui/OverrideSettingsScreen.kt | 935 +++++++++--------- .../clash/settings/ui/SettingsPreference.kt | 19 + 7 files changed, 965 insertions(+), 961 deletions(-) diff --git a/app/src/main/kotlin/com/github/kr328/clash/crash/ui/ApkBrokenScreen.kt b/app/src/main/kotlin/com/github/kr328/clash/crash/ui/ApkBrokenScreen.kt index be584b0ac4..eb01c69844 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/crash/ui/ApkBrokenScreen.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/crash/ui/ApkBrokenScreen.kt @@ -15,8 +15,8 @@ import com.github.kr328.clash.main.ui.openLink import com.github.kr328.clash.ui.component.MihomoScaffold import com.github.kr328.clash.ui.theme.MihomoTheme import com.github.kr328.clash.ui.theme.PreviewMihomo -import me.zhanghai.compose.preference.Preference import me.zhanghai.compose.preference.ProvidePreferenceLocals +import me.zhanghai.compose.preference.preference import me.zhanghai.compose.preference.preferenceCategory @OptIn(ExperimentalMaterial3Api::class) @@ -26,26 +26,24 @@ fun ApkBrokenScreen() { ProvidePreferenceLocals { val context = LocalContext.current LazyColumn(modifier = Modifier.fillMaxSize(), contentPadding = innerPadding) { - item(key = "tips_application_broken") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.application_broken)) }, - summary = { Text(stringResource(R.string.application_broken_tips)) }, - enabled = false, - ) - } + preference( + key = "tips_application_broken", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.application_broken)) }, + summary = { Text(stringResource(R.string.application_broken_tips)) }, + enabled = false, + ) preferenceCategory( key = "cat_reinstall", title = { Text(stringResource(R.string.reinstall)) }, ) - item(key = "github_releases") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.github_releases)) }, - summary = { Text(CMFA_GITHUB) }, - onClick = { context.openLink(CMFA_GITHUB) }, - ) - } + preference( + key = "github_releases", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.github_releases)) }, + summary = { Text(CMFA_GITHUB) }, + onClick = { context.openLink(CMFA_GITHUB) }, + ) } } } diff --git a/app/src/main/kotlin/com/github/kr328/clash/main/ui/HelpScreen.kt b/app/src/main/kotlin/com/github/kr328/clash/main/ui/HelpScreen.kt index 2b842b3c97..a576a2d47f 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/main/ui/HelpScreen.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/main/ui/HelpScreen.kt @@ -18,8 +18,8 @@ import com.github.kr328.clash.R import com.github.kr328.clash.ui.component.MihomoScaffold import com.github.kr328.clash.ui.theme.MihomoTheme import com.github.kr328.clash.ui.theme.PreviewMihomo -import me.zhanghai.compose.preference.Preference import me.zhanghai.compose.preference.ProvidePreferenceLocals +import me.zhanghai.compose.preference.preference import me.zhanghai.compose.preference.preferenceCategory @OptIn(ExperimentalMaterial3Api::class) @@ -29,51 +29,46 @@ fun HelpScreen(modifier: Modifier = Modifier) { ProvidePreferenceLocals { val context = LocalContext.current LazyColumn(modifier = Modifier.fillMaxSize(), contentPadding = innerPadding) { - item(key = "tips_help") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.help)) }, - summary = { Text(AnnotatedString.fromHtml(stringResource(R.string.tips_help))) }, - enabled = false, - ) - } + preference( + key = "tips_help", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.help)) }, + summary = { Text(AnnotatedString.fromHtml(stringResource(R.string.tips_help))) }, + enabled = false, + ) preferenceCategory( key = "cat_document", title = { Text(stringResource(R.string.document)) }, ) - item(key = "clash_wiki") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.clash_wiki)) }, - summary = { Text(CLASH_WIKI) }, - onClick = { context.openLink(CLASH_WIKI) }, - ) - } - item(key = "clash_meta_wiki") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.clash_meta_wiki)) }, - summary = { Text(CLASH_META_WIKI) }, - onClick = { context.openLink(CLASH_META_WIKI) }, - ) - } + preference( + key = "clash_wiki", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.clash_wiki)) }, + summary = { Text(CLASH_WIKI) }, + onClick = { context.openLink(CLASH_WIKI) }, + ) + preference( + key = "clash_meta_wiki", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.clash_meta_wiki)) }, + summary = { Text(CLASH_META_WIKI) }, + onClick = { context.openLink(CLASH_META_WIKI) }, + ) preferenceCategory(key = "cat_sources", title = { Text(stringResource(R.string.sources)) }) - item(key = "clash_meta_core") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.clash_meta_core)) }, - summary = { Text(CLASH_META_CORE) }, - onClick = { context.openLink(CLASH_META_CORE) }, - ) - } - item(key = "clash_meta_for_android") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.clash_meta_for_android)) }, - summary = { Text(CMFA_GITHUB) }, - onClick = { context.openLink(CMFA_GITHUB) }, - ) - } + preference( + key = "clash_meta_core", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.clash_meta_core)) }, + summary = { Text(CLASH_META_CORE) }, + onClick = { context.openLink(CLASH_META_CORE) }, + ) + preference( + key = "clash_meta_for_android", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.clash_meta_for_android)) }, + summary = { Text(CMFA_GITHUB) }, + onClick = { context.openLink(CMFA_GITHUB) }, + ) } } } diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/AppSettingsScreen.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/AppSettingsScreen.kt index 60cfe36134..c78600e229 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/AppSettingsScreen.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/AppSettingsScreen.kt @@ -24,10 +24,10 @@ import com.github.kr328.clash.ui.icon.BaselineStack import com.github.kr328.clash.ui.icon.MihomoIcons import com.github.kr328.clash.ui.theme.MihomoTheme import com.github.kr328.clash.ui.theme.PreviewMihomo -import me.zhanghai.compose.preference.ListPreference import me.zhanghai.compose.preference.ProvidePreferenceLocals -import me.zhanghai.compose.preference.SwitchPreference +import me.zhanghai.compose.preference.listPreference import me.zhanghai.compose.preference.preferenceCategory +import me.zhanghai.compose.preference.switchPreference @Composable fun AppSettingsScreen( @@ -69,65 +69,64 @@ private fun AppSettingsContent( key = "cat_behavior", title = { Text(stringResource(R.string.behavior)) }, ) - item(key = "auto_restart") { - SwitchPreference( - value = uiState.autoRestart, - onValueChange = onAutoRestartChange, - icon = { Icon(imageVector = MihomoIcons.BaselineRestore, contentDescription = null) }, - title = { Text(stringResource(R.string.auto_restart)) }, - summary = { Text(stringResource(R.string.allow_clash_auto_restart)) }, - ) - } + switchPreference( + key = "auto_restart", + defaultValue = uiState.autoRestart, + rememberState = { + rememberCallbackMutableState(uiState.autoRestart, onAutoRestartChange) + }, + icon = { Icon(imageVector = MihomoIcons.BaselineRestore, contentDescription = null) }, + title = { Text(stringResource(R.string.auto_restart)) }, + summary = { Text(stringResource(R.string.allow_clash_auto_restart)) }, + ) preferenceCategory( key = "cat_interface", title = { Text(stringResource(R.string.interface_)) }, ) - item(key = "dark_mode") { - ListPreference( - value = uiState.darkMode, - onValueChange = onDarkModeChange, - values = listOf(DarkMode.Auto, DarkMode.ForceLight, DarkMode.ForceDark), - icon = { - Icon(imageVector = MihomoIcons.BaselineBrightness4, contentDescription = null) - }, - title = { Text(stringResource(R.string.dark_mode)) }, - summary = { Text(stringResource(uiState.darkMode.summaryRes)) }, - valueToText = { - androidx.compose.ui.text.AnnotatedString(stringResource(it.summaryRes)) - }, - ) - } - item(key = "hide_app_icon") { - SwitchPreference( - value = uiState.hideAppIcon, - onValueChange = onHideAppIconChange, - icon = { Icon(imageVector = MihomoIcons.BaselineHide, contentDescription = null) }, - title = { Text(stringResource(R.string.hide_app_icon_title)) }, - summary = { Text(stringResource(R.string.hide_app_icon_desc)) }, - ) - } - item(key = "hide_from_recents") { - SwitchPreference( - value = uiState.hideFromRecents, - onValueChange = onHideFromRecentsChange, - icon = { Icon(imageVector = MihomoIcons.BaselineStack, contentDescription = null) }, - title = { Text(stringResource(R.string.hide_from_recents_title)) }, - summary = { Text(stringResource(R.string.hide_from_recents_desc)) }, - ) - } + listPreference( + key = "dark_mode", + defaultValue = uiState.darkMode, + values = listOf(DarkMode.Auto, DarkMode.ForceLight, DarkMode.ForceDark), + rememberState = { rememberCallbackMutableState(uiState.darkMode, onDarkModeChange) }, + icon = { Icon(imageVector = MihomoIcons.BaselineBrightness4, contentDescription = null) }, + title = { Text(stringResource(R.string.dark_mode)) }, + summary = { Text(stringResource(it.summaryRes)) }, + valueToText = { androidx.compose.ui.text.AnnotatedString(stringResource(it.summaryRes)) }, + ) + switchPreference( + key = "hide_app_icon", + defaultValue = uiState.hideAppIcon, + rememberState = { + rememberCallbackMutableState(uiState.hideAppIcon, onHideAppIconChange) + }, + icon = { Icon(imageVector = MihomoIcons.BaselineHide, contentDescription = null) }, + title = { Text(stringResource(R.string.hide_app_icon_title)) }, + summary = { Text(stringResource(R.string.hide_app_icon_desc)) }, + ) + switchPreference( + key = "hide_from_recents", + defaultValue = uiState.hideFromRecents, + rememberState = { + rememberCallbackMutableState(uiState.hideFromRecents, onHideFromRecentsChange) + }, + icon = { Icon(imageVector = MihomoIcons.BaselineStack, contentDescription = null) }, + title = { Text(stringResource(R.string.hide_from_recents_title)) }, + summary = { Text(stringResource(R.string.hide_from_recents_desc)) }, + ) preferenceCategory(key = "cat_service", title = { Text(stringResource(R.string.service)) }) - item(key = "show_traffic") { - SwitchPreference( - value = uiState.dynamicNotification, - onValueChange = onDynamicNotificationChange, - enabled = !clashRunning, - icon = { Icon(imageVector = MihomoIcons.BaselineDomain, contentDescription = null) }, - title = { Text(stringResource(R.string.show_traffic)) }, - summary = { Text(stringResource(R.string.show_traffic_summary)) }, - ) - } + switchPreference( + key = "show_traffic", + defaultValue = uiState.dynamicNotification, + rememberState = { + rememberCallbackMutableState(uiState.dynamicNotification, onDynamicNotificationChange) + }, + enabled = { !clashRunning }, + icon = { Icon(imageVector = MihomoIcons.BaselineDomain, contentDescription = null) }, + title = { Text(stringResource(R.string.show_traffic)) }, + summary = { Text(stringResource(R.string.show_traffic_summary)) }, + ) } } } diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/MetaFeatureSettingsScreen.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/MetaFeatureSettingsScreen.kt index 070bec98f0..a0fffbf01d 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/MetaFeatureSettingsScreen.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/MetaFeatureSettingsScreen.kt @@ -44,9 +44,9 @@ import com.github.kr328.clash.ui.icon.MihomoIcons import com.github.kr328.clash.ui.theme.MihomoTheme import com.github.kr328.clash.ui.theme.PreviewMihomo import kotlinx.serialization.Serializable -import me.zhanghai.compose.preference.ListPreference -import me.zhanghai.compose.preference.Preference import me.zhanghai.compose.preference.ProvidePreferenceLocals +import me.zhanghai.compose.preference.listPreference +import me.zhanghai.compose.preference.preference import me.zhanghai.compose.preference.preferenceCategory private sealed interface MetaFeatureSettingsRoute : NavKey { @@ -247,50 +247,54 @@ private fun LazyListScope.metaBasicPreferenceItems( ) { preferenceCategory(key = "cat_settings", title = { Text(stringResource(R.string.settings)) }) - item(key = "unifiedDelay", contentType = "ListPreference") { - ListPreference( - value = configuration.unifiedDelay, - onValueChange = actions::updateUnifiedDelay, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.unified_delay)) }, - summary = { Text(stringResource(configuration.unifiedDelay.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "geodataMode", contentType = "ListPreference") { - ListPreference( - value = configuration.geodataMode, - onValueChange = actions::updateGeodataMode, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.geodata_mode)) }, - summary = { Text(stringResource(configuration.geodataMode.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "tcpConcurrent", contentType = "ListPreference") { - ListPreference( - value = configuration.tcpConcurrent, - onValueChange = actions::updateTcpConcurrent, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.tcp_concurrent)) }, - summary = { Text(stringResource(configuration.tcpConcurrent.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "findProcessMode", contentType = "ListPreference") { - ListPreference( - value = configuration.findProcessMode, - onValueChange = actions::updateFindProcessMode, - values = ConfigurationOverride.FindProcessMode.entries, - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.find_process_mode)) }, - summary = { Text(stringResource(configuration.findProcessMode.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } + listPreference( + key = "unifiedDelay", + defaultValue = configuration.unifiedDelay, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState(configuration.unifiedDelay, actions::updateUnifiedDelay) + }, + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.unified_delay)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + listPreference( + key = "geodataMode", + defaultValue = configuration.geodataMode, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState(configuration.geodataMode, actions::updateGeodataMode) + }, + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.geodata_mode)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + listPreference( + key = "tcpConcurrent", + defaultValue = configuration.tcpConcurrent, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState(configuration.tcpConcurrent, actions::updateTcpConcurrent) + }, + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.tcp_concurrent)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + listPreference( + key = "findProcessMode", + defaultValue = configuration.findProcessMode, + values = ConfigurationOverride.FindProcessMode.entries, + rememberState = { + rememberCallbackMutableState(configuration.findProcessMode, actions::updateFindProcessMode) + }, + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.find_process_mode)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) } private fun LazyListScope.metaSnifferPreferenceItems( @@ -302,214 +306,209 @@ private fun LazyListScope.metaSnifferPreferenceItems( key = "cat_sniffer", title = { Text(stringResource(R.string.sniffer_setting)) }, ) - - item(key = "snifferEnable", contentType = "ListPreference") { - ListPreference( - value = configuration.sniffer.enable, - onValueChange = actions::updateSnifferEnable, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.strategy)) }, - summary = { Text(stringResource(configuration.sniffer.enable.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "sniffHttpPorts", contentType = "EditTextListPreference") { - val enabled = configuration.sniffer.enable != false - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.sniff_http_ports)) }, - summary = { Text(configuration.sniffer.sniff.http.ports.listSummary(R.string.dont_modify)) }, - enabled = enabled, - onClick = { - onOpenEditableTextList( - R.string.sniff_http_ports, - configuration.sniffer.sniff.http.ports, - actions::updateSniffHttpPorts, - ) - }, - ) - } - item(key = "sniffHttpOverrideDestination", contentType = "ListPreference") { - val enabled = configuration.sniffer.enable != false - ListPreference( - value = configuration.sniffer.sniff.http.overrideDestination, - onValueChange = actions::updateSniffHttpOverrideDestination, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - enabled = enabled, - title = { Text(stringResource(R.string.sniff_http_override_destination)) }, - summary = { - Text(stringResource(configuration.sniffer.sniff.http.overrideDestination.textRes)) - }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "sniffTlsPorts", contentType = "EditTextListPreference") { - val enabled = configuration.sniffer.enable != false - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.sniff_tls_ports)) }, - summary = { Text(configuration.sniffer.sniff.tls.ports.listSummary(R.string.dont_modify)) }, - enabled = enabled, - onClick = { - onOpenEditableTextList( - R.string.sniff_tls_ports, - configuration.sniffer.sniff.tls.ports, - actions::updateSniffTlsPorts, - ) - }, - ) - } - item(key = "sniffTlsOverrideDestination", contentType = "ListPreference") { - val enabled = configuration.sniffer.enable != false - ListPreference( - value = configuration.sniffer.sniff.tls.overrideDestination, - onValueChange = actions::updateSniffTlsOverrideDestination, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - enabled = enabled, - title = { Text(stringResource(R.string.sniff_tls_override_destination)) }, - summary = { - Text(stringResource(configuration.sniffer.sniff.tls.overrideDestination.textRes)) - }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "sniffQuicPorts", contentType = "EditTextListPreference") { - val enabled = configuration.sniffer.enable != false - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.sniff_quic_ports)) }, - summary = { Text(configuration.sniffer.sniff.quic.ports.listSummary(R.string.dont_modify)) }, - enabled = enabled, - onClick = { - onOpenEditableTextList( - R.string.sniff_quic_ports, - configuration.sniffer.sniff.quic.ports, - actions::updateSniffQuicPorts, - ) - }, - ) - } - item(key = "sniffQuicOverrideDestination", contentType = "ListPreference") { - val enabled = configuration.sniffer.enable != false - ListPreference( - value = configuration.sniffer.sniff.quic.overrideDestination, - onValueChange = actions::updateSniffQuicOverrideDestination, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - enabled = enabled, - title = { Text(stringResource(R.string.sniff_quic_override_destination)) }, - summary = { - Text(stringResource(configuration.sniffer.sniff.quic.overrideDestination.textRes)) - }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "forceDnsMapping", contentType = "ListPreference") { - val enabled = configuration.sniffer.enable != false - ListPreference( - value = configuration.sniffer.forceDnsMapping, - onValueChange = actions::updateForceDnsMapping, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - enabled = enabled, - title = { Text(stringResource(R.string.force_dns_mapping)) }, - summary = { Text(stringResource(configuration.sniffer.forceDnsMapping.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "parsePureIp", contentType = "ListPreference") { - val enabled = configuration.sniffer.enable != false - ListPreference( - value = configuration.sniffer.parsePureIp, - onValueChange = actions::updateParsePureIp, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - enabled = enabled, - title = { Text(stringResource(R.string.parse_pure_ip)) }, - summary = { Text(stringResource(configuration.sniffer.parsePureIp.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "overrideDestination", contentType = "ListPreference") { - val enabled = configuration.sniffer.enable != false - ListPreference( - value = configuration.sniffer.overrideDestination, - onValueChange = actions::updateOverrideDestination, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - enabled = enabled, - title = { Text(stringResource(R.string.override_destination)) }, - summary = { Text(stringResource(configuration.sniffer.overrideDestination.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "forceDomain", contentType = "EditTextListPreference") { - val enabled = configuration.sniffer.enable != false - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.force_domain)) }, - summary = { Text(configuration.sniffer.forceDomain.listSummary(R.string.dont_modify)) }, - enabled = enabled, - onClick = { - onOpenEditableTextList( - R.string.force_domain, - configuration.sniffer.forceDomain, - actions::updateForceDomain, - ) - }, - ) - } - item(key = "skipDomain", contentType = "EditTextListPreference") { - val enabled = configuration.sniffer.enable != false - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.skip_domain)) }, - summary = { Text(configuration.sniffer.skipDomain.listSummary(R.string.dont_modify)) }, - enabled = enabled, - onClick = { - onOpenEditableTextList( - R.string.skip_domain, - configuration.sniffer.skipDomain, - actions::updateSkipDomain, - ) - }, - ) - } - item(key = "skipSrcAddress", contentType = "EditTextListPreference") { - val enabled = configuration.sniffer.enable != false - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.skip_src_address)) }, - summary = { Text(configuration.sniffer.skipSrcAddress.listSummary(R.string.dont_modify)) }, - enabled = enabled, - onClick = { - onOpenEditableTextList( - R.string.skip_src_address, - configuration.sniffer.skipSrcAddress, - actions::updateSkipSrcAddress, - ) - }, - ) - } - item(key = "skipDstAddress", contentType = "EditTextListPreference") { - val enabled = configuration.sniffer.enable != false - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.skip_dst_address)) }, - summary = { Text(configuration.sniffer.skipDstAddress.listSummary(R.string.dont_modify)) }, - enabled = enabled, - onClick = { - onOpenEditableTextList( - R.string.skip_dst_address, - configuration.sniffer.skipDstAddress, - actions::updateSkipDstAddress, - ) - }, - ) - } + listPreference( + key = "snifferEnable", + defaultValue = configuration.sniffer.enable, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState(configuration.sniffer.enable, actions::updateSnifferEnable) + }, + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.strategy)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + preference( + key = "sniffHttpPorts", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.sniff_http_ports)) }, + summary = { Text(configuration.sniffer.sniff.http.ports.listSummary(R.string.dont_modify)) }, + enabled = configuration.sniffer.enable != false, + onClick = { + onOpenEditableTextList( + R.string.sniff_http_ports, + configuration.sniffer.sniff.http.ports, + actions::updateSniffHttpPorts, + ) + }, + ) + listPreference( + key = "sniffHttpOverrideDestination", + defaultValue = configuration.sniffer.sniff.http.overrideDestination, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState( + configuration.sniffer.sniff.http.overrideDestination, + actions::updateSniffHttpOverrideDestination, + ) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { configuration.sniffer.enable != false }, + title = { Text(stringResource(R.string.sniff_http_override_destination)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + preference( + key = "sniffTlsPorts", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.sniff_tls_ports)) }, + summary = { Text(configuration.sniffer.sniff.tls.ports.listSummary(R.string.dont_modify)) }, + enabled = configuration.sniffer.enable != false, + onClick = { + onOpenEditableTextList( + R.string.sniff_tls_ports, + configuration.sniffer.sniff.tls.ports, + actions::updateSniffTlsPorts, + ) + }, + ) + listPreference( + key = "sniffTlsOverrideDestination", + defaultValue = configuration.sniffer.sniff.tls.overrideDestination, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState( + configuration.sniffer.sniff.tls.overrideDestination, + actions::updateSniffTlsOverrideDestination, + ) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { configuration.sniffer.enable != false }, + title = { Text(stringResource(R.string.sniff_tls_override_destination)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + preference( + key = "sniffQuicPorts", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.sniff_quic_ports)) }, + summary = { Text(configuration.sniffer.sniff.quic.ports.listSummary(R.string.dont_modify)) }, + enabled = configuration.sniffer.enable != false, + onClick = { + onOpenEditableTextList( + R.string.sniff_quic_ports, + configuration.sniffer.sniff.quic.ports, + actions::updateSniffQuicPorts, + ) + }, + ) + listPreference( + key = "sniffQuicOverrideDestination", + defaultValue = configuration.sniffer.sniff.quic.overrideDestination, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState( + configuration.sniffer.sniff.quic.overrideDestination, + actions::updateSniffQuicOverrideDestination, + ) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { configuration.sniffer.enable != false }, + title = { Text(stringResource(R.string.sniff_quic_override_destination)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + listPreference( + key = "forceDnsMapping", + defaultValue = configuration.sniffer.forceDnsMapping, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState( + configuration.sniffer.forceDnsMapping, + actions::updateForceDnsMapping, + ) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { configuration.sniffer.enable != false }, + title = { Text(stringResource(R.string.force_dns_mapping)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + listPreference( + key = "parsePureIp", + defaultValue = configuration.sniffer.parsePureIp, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState(configuration.sniffer.parsePureIp, actions::updateParsePureIp) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { configuration.sniffer.enable != false }, + title = { Text(stringResource(R.string.parse_pure_ip)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + listPreference( + key = "overrideDestination", + defaultValue = configuration.sniffer.overrideDestination, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState( + configuration.sniffer.overrideDestination, + actions::updateOverrideDestination, + ) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { configuration.sniffer.enable != false }, + title = { Text(stringResource(R.string.override_destination)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + preference( + key = "forceDomain", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.force_domain)) }, + summary = { Text(configuration.sniffer.forceDomain.listSummary(R.string.dont_modify)) }, + enabled = configuration.sniffer.enable != false, + onClick = { + onOpenEditableTextList( + R.string.force_domain, + configuration.sniffer.forceDomain, + actions::updateForceDomain, + ) + }, + ) + preference( + key = "skipDomain", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.skip_domain)) }, + summary = { Text(configuration.sniffer.skipDomain.listSummary(R.string.dont_modify)) }, + enabled = configuration.sniffer.enable != false, + onClick = { + onOpenEditableTextList( + R.string.skip_domain, + configuration.sniffer.skipDomain, + actions::updateSkipDomain, + ) + }, + ) + preference( + key = "skipSrcAddress", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.skip_src_address)) }, + summary = { Text(configuration.sniffer.skipSrcAddress.listSummary(R.string.dont_modify)) }, + enabled = configuration.sniffer.enable != false, + onClick = { + onOpenEditableTextList( + R.string.skip_src_address, + configuration.sniffer.skipSrcAddress, + actions::updateSkipSrcAddress, + ) + }, + ) + preference( + key = "skipDstAddress", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.skip_dst_address)) }, + summary = { Text(configuration.sniffer.skipDstAddress.listSummary(R.string.dont_modify)) }, + enabled = configuration.sniffer.enable != false, + onClick = { + onOpenEditableTextList( + R.string.skip_dst_address, + configuration.sniffer.skipDstAddress, + actions::updateSkipDstAddress, + ) + }, + ) } private fun LazyListScope.metaGeoFileItems( @@ -520,38 +519,34 @@ private fun LazyListScope.metaGeoFileItems( ) { preferenceCategory(key = "cat_geox", title = { Text(stringResource(R.string.geox_files)) }) - item(key = "importGeoIp", contentType = "ClickablePreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.import_geoip_file)) }, - summary = { Text(stringResource(R.string.press_to_import)) }, - onClick = onImportGeoIp, - ) - } - item(key = "importGeoSite", contentType = "ClickablePreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.import_geosite_file)) }, - summary = { Text(stringResource(R.string.press_to_import)) }, - onClick = onImportGeoSite, - ) - } - item(key = "importCountry", contentType = "ClickablePreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.import_country_file)) }, - summary = { Text(stringResource(R.string.press_to_import)) }, - onClick = onImportCountry, - ) - } - item(key = "importASN", contentType = "ClickablePreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.import_asn_file)) }, - summary = { Text(stringResource(R.string.press_to_import)) }, - onClick = onImportASN, - ) - } + preference( + key = "importGeoIp", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.import_geoip_file)) }, + summary = { Text(stringResource(R.string.press_to_import)) }, + onClick = onImportGeoIp, + ) + preference( + key = "importGeoSite", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.import_geosite_file)) }, + summary = { Text(stringResource(R.string.press_to_import)) }, + onClick = onImportGeoSite, + ) + preference( + key = "importCountry", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.import_country_file)) }, + summary = { Text(stringResource(R.string.press_to_import)) }, + onClick = onImportCountry, + ) + preference( + key = "importASN", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.import_asn_file)) }, + summary = { Text(stringResource(R.string.press_to_import)) }, + onClick = onImportASN, + ) } private val ConfigurationOverride.FindProcessMode?.textRes: Int diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/NetworkSettingsScreen.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/NetworkSettingsScreen.kt index a47e97f9d6..b40872a6a3 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/NetworkSettingsScreen.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/NetworkSettingsScreen.kt @@ -25,11 +25,11 @@ import com.github.kr328.clash.ui.icon.BaselineVpnLock import com.github.kr328.clash.ui.icon.MihomoIcons import com.github.kr328.clash.ui.theme.MihomoTheme import com.github.kr328.clash.ui.theme.PreviewMihomo -import me.zhanghai.compose.preference.ListPreference -import me.zhanghai.compose.preference.Preference import me.zhanghai.compose.preference.ProvidePreferenceLocals -import me.zhanghai.compose.preference.SwitchPreference +import me.zhanghai.compose.preference.listPreference +import me.zhanghai.compose.preference.preference import me.zhanghai.compose.preference.preferenceCategory +import me.zhanghai.compose.preference.switchPreference @Composable fun NetworkSettingsScreen( @@ -95,108 +95,107 @@ private fun NetworkSettingsContent( ) { innerPadding -> ProvidePreferenceLocals { LazyColumn(modifier = Modifier.fillMaxSize(), contentPadding = innerPadding) { - item(key = "route_system_traffic") { - SwitchPreference( - value = uiState.enableVpn, - onValueChange = onEnableVpnChange, - enabled = !clashRunning, - icon = { Icon(imageVector = MihomoIcons.BaselineVpnLock, contentDescription = null) }, - title = { Text(stringResource(R.string.route_system_traffic)) }, - summary = { Text(stringResource(R.string.routing_via_vpn_service)) }, - ) - } + switchPreference( + key = "route_system_traffic", + defaultValue = uiState.enableVpn, + rememberState = { rememberCallbackMutableState(uiState.enableVpn, onEnableVpnChange) }, + enabled = { !clashRunning }, + icon = { Icon(imageVector = MihomoIcons.BaselineVpnLock, contentDescription = null) }, + title = { Text(stringResource(R.string.route_system_traffic)) }, + summary = { Text(stringResource(R.string.routing_via_vpn_service)) }, + ) preferenceCategory( key = "cat_vpn_service_options", title = { Text(stringResource(R.string.vpn_service_options)) }, ) - item(key = "bypass_private_network") { - SwitchPreference( - value = uiState.bypassPrivateNetwork, - onValueChange = onBypassPrivateNetworkChange, - enabled = vpnDependenciesEnabled, - title = { Text(stringResource(R.string.bypass_private_network)) }, - summary = { Text(stringResource(R.string.bypass_private_network_summary)) }, - ) - } - item(key = "dns_hijacking") { - SwitchPreference( - value = uiState.dnsHijacking, - onValueChange = onDnsHijackingChange, - enabled = vpnDependenciesEnabled, - title = { Text(stringResource(R.string.dns_hijacking)) }, - summary = { Text(stringResource(R.string.dns_hijacking_summary)) }, - ) - } - item(key = "allow_bypass") { - SwitchPreference( - value = uiState.allowBypass, - onValueChange = onAllowBypassChange, - enabled = vpnDependenciesEnabled, - title = { Text(stringResource(R.string.allow_bypass)) }, - summary = { Text(stringResource(R.string.allow_bypass_summary)) }, - ) - } - item(key = "allow_ipv6") { - SwitchPreference( - value = uiState.allowIpv6, - onValueChange = onAllowIpv6Change, - enabled = vpnDependenciesEnabled, - title = { Text(stringResource(R.string.allow_ipv6)) }, - summary = { Text(stringResource(R.string.allow_ipv6_summary)) }, - ) - } + switchPreference( + key = "bypass_private_network", + defaultValue = uiState.bypassPrivateNetwork, + rememberState = { + rememberCallbackMutableState(uiState.bypassPrivateNetwork, onBypassPrivateNetworkChange) + }, + enabled = { vpnDependenciesEnabled }, + title = { Text(stringResource(R.string.bypass_private_network)) }, + summary = { Text(stringResource(R.string.bypass_private_network_summary)) }, + ) + switchPreference( + key = "dns_hijacking", + defaultValue = uiState.dnsHijacking, + rememberState = { + rememberCallbackMutableState(uiState.dnsHijacking, onDnsHijackingChange) + }, + enabled = { vpnDependenciesEnabled }, + title = { Text(stringResource(R.string.dns_hijacking)) }, + summary = { Text(stringResource(R.string.dns_hijacking_summary)) }, + ) + switchPreference( + key = "allow_bypass", + defaultValue = uiState.allowBypass, + rememberState = { + rememberCallbackMutableState(uiState.allowBypass, onAllowBypassChange) + }, + enabled = { vpnDependenciesEnabled }, + title = { Text(stringResource(R.string.allow_bypass)) }, + summary = { Text(stringResource(R.string.allow_bypass_summary)) }, + ) + switchPreference( + key = "allow_ipv6", + defaultValue = uiState.allowIpv6, + rememberState = { rememberCallbackMutableState(uiState.allowIpv6, onAllowIpv6Change) }, + enabled = { vpnDependenciesEnabled }, + title = { Text(stringResource(R.string.allow_ipv6)) }, + summary = { Text(stringResource(R.string.allow_ipv6_summary)) }, + ) if (uiState.hasSystemProxyOption) { - item(key = "system_proxy") { - SwitchPreference( - value = uiState.systemProxy, - onValueChange = onSystemProxyChange, - enabled = vpnDependenciesEnabled, - title = { Text(stringResource(R.string.system_proxy)) }, - summary = { Text(stringResource(R.string.system_proxy_summary)) }, - ) - } - } - item(key = "tun_stack_mode") { - ListPreference( - value = tunStackMode, - onValueChange = { onTunStackModeChange(it.persistedValue) }, - values = TunStackMode.entries, - enabled = vpnDependenciesEnabled, - title = { Text(stringResource(R.string.tun_stack_mode)) }, - summary = { Text(stringResource(tunStackMode.summaryRes)) }, - valueToText = { - androidx.compose.ui.text.AnnotatedString(stringResource(it.summaryRes)) - }, - ) - } - item(key = "access_control_mode") { - ListPreference( - value = uiState.accessControlMode, - onValueChange = onAccessControlModeChange, - values = - listOf( - AccessControlMode.AcceptAll, - AccessControlMode.AcceptSelected, - AccessControlMode.DenySelected, - ), - enabled = vpnDependenciesEnabled, - title = { Text(stringResource(R.string.access_control_mode)) }, - summary = { Text(stringResource(uiState.accessControlMode.summaryRes)) }, - valueToText = { - androidx.compose.ui.text.AnnotatedString(stringResource(it.summaryRes)) + switchPreference( + key = "system_proxy", + defaultValue = uiState.systemProxy, + rememberState = { + rememberCallbackMutableState(uiState.systemProxy, onSystemProxyChange) }, + enabled = { vpnDependenciesEnabled }, + title = { Text(stringResource(R.string.system_proxy)) }, + summary = { Text(stringResource(R.string.system_proxy_summary)) }, ) } - item(key = "access_control_packages") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.access_control_packages)) }, - summary = { Text(stringResource(R.string.access_control_packages_summary)) }, - onClick = onAccessControlPackagesClick, - ) - } + listPreference( + key = "tun_stack_mode", + defaultValue = tunStackMode, + values = TunStackMode.entries, + rememberState = { + rememberCallbackMutableState(tunStackMode) { onTunStackModeChange(it.persistedValue) } + }, + enabled = { vpnDependenciesEnabled }, + title = { Text(stringResource(R.string.tun_stack_mode)) }, + summary = { Text(stringResource(it.summaryRes)) }, + valueToText = { androidx.compose.ui.text.AnnotatedString(stringResource(it.summaryRes)) }, + ) + listPreference( + key = "access_control_mode", + defaultValue = uiState.accessControlMode, + values = + listOf( + AccessControlMode.AcceptAll, + AccessControlMode.AcceptSelected, + AccessControlMode.DenySelected, + ), + rememberState = { + rememberCallbackMutableState(uiState.accessControlMode, onAccessControlModeChange) + }, + enabled = { vpnDependenciesEnabled }, + title = { Text(stringResource(R.string.access_control_mode)) }, + summary = { Text(stringResource(it.summaryRes)) }, + valueToText = { androidx.compose.ui.text.AnnotatedString(stringResource(it.summaryRes)) }, + ) + preference( + key = "access_control_packages", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.access_control_packages)) }, + summary = { Text(stringResource(R.string.access_control_packages_summary)) }, + onClick = onAccessControlPackagesClick, + ) } } } diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt index 1d742685d6..d989ee1b99 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt @@ -50,9 +50,10 @@ import com.github.kr328.clash.ui.icon.MihomoIcons import com.github.kr328.clash.ui.theme.MihomoTheme import com.github.kr328.clash.ui.theme.PreviewMihomo import kotlinx.serialization.Serializable -import me.zhanghai.compose.preference.ListPreference import me.zhanghai.compose.preference.Preference import me.zhanghai.compose.preference.ProvidePreferenceLocals +import me.zhanghai.compose.preference.listPreference +import me.zhanghai.compose.preference.preference import me.zhanghai.compose.preference.preferenceCategory private sealed interface OverrideSettingsRoute : NavKey { @@ -208,187 +209,177 @@ private fun LazyListScope.generalPreferenceItems( onOpenEditableTextList: (Int, List?, (List?) -> Unit) -> Unit, ) { preferenceCategory(key = "cat_general", title = { Text(stringResource(R.string.general)) }) - item(key = "httpPort", contentType = "EditTextPreference") { - OverrideEditTextPreferenceItem( - title = R.string.http_port, - placeholder = R.string.dont_modify, - emptyLabel = R.string.disabled, - value = portText(configuration.httpPort), - onValueChange = { actions.updateHttpPort(parsePort(it)) }, - numericOnly = true, - ) - } - item(key = "socksPort", contentType = "EditTextPreference") { - OverrideEditTextPreferenceItem( - title = R.string.socks_port, - placeholder = R.string.dont_modify, - emptyLabel = R.string.disabled, - value = portText(configuration.socksPort), - onValueChange = { actions.updateSocksPort(parsePort(it)) }, - numericOnly = true, - ) - } - item(key = "redirectPort", contentType = "EditTextPreference") { - OverrideEditTextPreferenceItem( - title = R.string.redirect_port, - placeholder = R.string.dont_modify, - emptyLabel = R.string.disabled, - value = portText(configuration.redirectPort), - onValueChange = { actions.updateRedirectPort(parsePort(it)) }, - numericOnly = true, - ) - } - item(key = "tproxyPort", contentType = "EditTextPreference") { - OverrideEditTextPreferenceItem( - title = R.string.tproxy_port, - placeholder = R.string.dont_modify, - emptyLabel = R.string.disabled, - value = portText(configuration.tproxyPort), - onValueChange = { actions.updateTproxyPort(parsePort(it)) }, - numericOnly = true, - ) - } - item(key = "mixedPort", contentType = "EditTextPreference") { - OverrideEditTextPreferenceItem( - title = R.string.mixed_port, - placeholder = R.string.dont_modify, - emptyLabel = R.string.disabled, - value = portText(configuration.mixedPort), - onValueChange = { actions.updateMixedPort(parsePort(it)) }, - numericOnly = true, - ) - } - item(key = "authentication", contentType = "EditTextListPreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.authentication)) }, - summary = { Text(configuration.authentication.listSummary(R.string.dont_modify)) }, - onClick = { - onOpenEditableTextList( - R.string.authentication, - configuration.authentication, - actions::updateAuthentication, - ) - }, - ) - } - item(key = "allowLan", contentType = "ListPreference") { - ListPreference( - value = configuration.allowLan, - onValueChange = actions::updateAllowLan, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.allow_lan)) }, - summary = { Text(stringResource(configuration.allowLan.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "ipv6", contentType = "ListPreference") { - ListPreference( - value = configuration.ipv6, - onValueChange = actions::updateIpv6, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.ipv6)) }, - summary = { Text(stringResource(configuration.ipv6.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "bindAddress", contentType = "EditTextPreference") { - OverrideEditTextPreferenceItem( - title = R.string.bind_address, - placeholder = R.string.dont_modify, - emptyLabel = R.string.default_, - value = configuration.bindAddress, - onValueChange = actions::updateBindAddress, - ) - } - item(key = "externalController", contentType = "EditTextPreference") { - OverrideEditTextPreferenceItem( - title = R.string.external_controller, - placeholder = R.string.dont_modify, - emptyLabel = R.string.default_, - value = configuration.externalController, - onValueChange = actions::updateExternalController, - ) - } - item(key = "externalControllerTls", contentType = "EditTextPreference") { - OverrideEditTextPreferenceItem( - title = R.string.external_controller_tls, - placeholder = R.string.dont_modify, - emptyLabel = R.string.default_, - value = configuration.externalControllerTLS, - onValueChange = actions::updateExternalControllerTls, - ) - } - item(key = "allowOrigins", contentType = "EditTextListPreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.allow_origins)) }, - summary = { - Text(configuration.externalControllerCors.allowOrigins.listSummary(R.string.dont_modify)) - }, - onClick = { - onOpenEditableTextList( - R.string.allow_origins, - configuration.externalControllerCors.allowOrigins, - actions::updateAllowOrigins, - ) - }, - ) - } - item(key = "allowPrivateNetwork", contentType = "ListPreference") { - ListPreference( - value = configuration.externalControllerCors.allowPrivateNetwork, - onValueChange = actions::updateAllowPrivateNetwork, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.allow_private_network)) }, - summary = { - Text(stringResource(configuration.externalControllerCors.allowPrivateNetwork.textRes)) - }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "secret", contentType = "EditTextPreference") { - OverrideEditTextPreferenceItem( - title = R.string.secret, - placeholder = R.string.dont_modify, - emptyLabel = R.string.default_, - value = configuration.secret, - onValueChange = actions::updateSecret, - ) - } - item(key = "mode", contentType = "ListPreference") { - ListPreference( - value = configuration.mode, - onValueChange = actions::updateMode, - values = TunnelState.Mode.entries, - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.mode)) }, - summary = { Text(stringResource(configuration.mode.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "logLevel", contentType = "ListPreference") { - ListPreference( - value = configuration.logLevel, - onValueChange = actions::updateLogLevel, - values = LogMessage.Level.entries, - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.log_level)) }, - summary = { Text(stringResource(configuration.logLevel.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "hosts", contentType = "EditTextMapPreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.hosts)) }, - summary = { Text(configuration.hosts.summary(R.string.dont_modify)) }, - onClick = { onOpenEditableTextMap(R.string.hosts, configuration.hosts, actions::updateHosts) }, - ) - } + OverrideEditTextPreferenceItem( + key = "httpPort", + title = R.string.http_port, + placeholder = R.string.dont_modify, + emptyLabel = R.string.disabled, + value = portText(configuration.httpPort), + onValueChange = { actions.updateHttpPort(parsePort(it)) }, + numericOnly = true, + ) + OverrideEditTextPreferenceItem( + key = "socksPort", + title = R.string.socks_port, + placeholder = R.string.dont_modify, + emptyLabel = R.string.disabled, + value = portText(configuration.socksPort), + onValueChange = { actions.updateSocksPort(parsePort(it)) }, + numericOnly = true, + ) + OverrideEditTextPreferenceItem( + key = "redirectPort", + title = R.string.redirect_port, + placeholder = R.string.dont_modify, + emptyLabel = R.string.disabled, + value = portText(configuration.redirectPort), + onValueChange = { actions.updateRedirectPort(parsePort(it)) }, + numericOnly = true, + ) + OverrideEditTextPreferenceItem( + key = "tproxyPort", + title = R.string.tproxy_port, + placeholder = R.string.dont_modify, + emptyLabel = R.string.disabled, + value = portText(configuration.tproxyPort), + onValueChange = { actions.updateTproxyPort(parsePort(it)) }, + numericOnly = true, + ) + OverrideEditTextPreferenceItem( + key = "mixedPort", + title = R.string.mixed_port, + placeholder = R.string.dont_modify, + emptyLabel = R.string.disabled, + value = portText(configuration.mixedPort), + onValueChange = { actions.updateMixedPort(parsePort(it)) }, + numericOnly = true, + ) + preference( + key = "authentication", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.authentication)) }, + summary = { Text(configuration.authentication.listSummary(R.string.dont_modify)) }, + onClick = { + onOpenEditableTextList( + R.string.authentication, + configuration.authentication, + actions::updateAuthentication, + ) + }, + ) + listPreference( + key = "allowLan", + defaultValue = configuration.allowLan, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState(configuration.allowLan, actions::updateAllowLan) + }, + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.allow_lan)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + listPreference( + key = "ipv6", + defaultValue = configuration.ipv6, + values = booleanOptions, + rememberState = { rememberCallbackMutableState(configuration.ipv6, actions::updateIpv6) }, + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.ipv6)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + OverrideEditTextPreferenceItem( + key = "bindAddress", + title = R.string.bind_address, + placeholder = R.string.dont_modify, + emptyLabel = R.string.default_, + value = configuration.bindAddress, + onValueChange = actions::updateBindAddress, + ) + OverrideEditTextPreferenceItem( + key = "externalController", + title = R.string.external_controller, + placeholder = R.string.dont_modify, + emptyLabel = R.string.default_, + value = configuration.externalController, + onValueChange = actions::updateExternalController, + ) + OverrideEditTextPreferenceItem( + key = "externalControllerTls", + title = R.string.external_controller_tls, + placeholder = R.string.dont_modify, + emptyLabel = R.string.default_, + value = configuration.externalControllerTLS, + onValueChange = actions::updateExternalControllerTls, + ) + preference( + key = "allowOrigins", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.allow_origins)) }, + summary = { + Text(configuration.externalControllerCors.allowOrigins.listSummary(R.string.dont_modify)) + }, + onClick = { + onOpenEditableTextList( + R.string.allow_origins, + configuration.externalControllerCors.allowOrigins, + actions::updateAllowOrigins, + ) + }, + ) + listPreference( + key = "allowPrivateNetwork", + defaultValue = configuration.externalControllerCors.allowPrivateNetwork, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState( + configuration.externalControllerCors.allowPrivateNetwork, + actions::updateAllowPrivateNetwork, + ) + }, + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.allow_private_network)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + OverrideEditTextPreferenceItem( + key = "secret", + title = R.string.secret, + placeholder = R.string.dont_modify, + emptyLabel = R.string.default_, + value = configuration.secret, + onValueChange = actions::updateSecret, + ) + listPreference( + key = "mode", + defaultValue = configuration.mode, + values = TunnelState.Mode.entries, + rememberState = { rememberCallbackMutableState(configuration.mode, actions::updateMode) }, + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.mode)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + listPreference( + key = "logLevel", + defaultValue = configuration.logLevel, + values = LogMessage.Level.entries, + rememberState = { + rememberCallbackMutableState(configuration.logLevel, actions::updateLogLevel) + }, + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.log_level)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + preference( + key = "hosts", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.hosts)) }, + summary = { Text(configuration.hosts.summary(R.string.dont_modify)) }, + onClick = { onOpenEditableTextMap(R.string.hosts, configuration.hosts, actions::updateHosts) }, + ) } private fun LazyListScope.dnsPreferenceItems( @@ -399,230 +390,236 @@ private fun LazyListScope.dnsPreferenceItems( onOpenEditableTextList: (Int, List?, (List?) -> Unit) -> Unit, ) { preferenceCategory(key = "cat_dns", title = { Text(stringResource(R.string.dns)) }) - item(key = "dnsStrategy", contentType = "ListPreference") { - ListPreference( - value = dnsEnabled, - onValueChange = actions::updateDnsEnable, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.strategy)) }, - summary = { Text(stringResource(dnsEnabled.dnsStrategyTextRes)) }, - valueToText = { AnnotatedString(stringResource(it.dnsStrategyTextRes)) }, - ) - } - item(key = "dnsPreferH3", contentType = "ListPreference") { - ListPreference( - value = configuration.dns.preferH3, - onValueChange = actions::updateDnsPreferH3, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - enabled = dnsEnabled != false, - title = { Text(stringResource(R.string.prefer_h3)) }, - summary = { Text(stringResource(configuration.dns.preferH3.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "dnsListen", contentType = "EditTextPreference") { - OverrideEditTextPreferenceItem( - title = R.string.listen, - placeholder = R.string.dont_modify, - emptyLabel = R.string.disabled, - value = configuration.dns.listen, - onValueChange = actions::updateDnsListen, - enabled = dnsEnabled != false, - ) - } - item(key = "appendSystemDns", contentType = "ListPreference") { - ListPreference( - value = configuration.app.appendSystemDns, - onValueChange = actions::updateAppendSystemDns, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - enabled = dnsEnabled != false, - title = { Text(stringResource(R.string.append_system_dns)) }, - summary = { Text(stringResource(configuration.app.appendSystemDns.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "dnsIpv6", contentType = "ListPreference") { - ListPreference( - value = configuration.dns.ipv6, - onValueChange = actions::updateDnsIpv6, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - enabled = dnsEnabled != false, - title = { Text(stringResource(R.string.ipv6)) }, - summary = { Text(stringResource(configuration.dns.ipv6.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "dnsUseHosts", contentType = "ListPreference") { - ListPreference( - value = configuration.dns.useHosts, - onValueChange = actions::updateDnsUseHosts, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - enabled = dnsEnabled != false, - title = { Text(stringResource(R.string.use_hosts)) }, - summary = { Text(stringResource(configuration.dns.useHosts.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "dnsEnhancedMode", contentType = "ListPreference") { - ListPreference( - value = configuration.dns.enhancedMode, - onValueChange = actions::updateDnsEnhancedMode, - values = ConfigurationOverride.DnsEnhancedMode.entries, - modifier = Modifier.fillMaxWidth(), - enabled = dnsEnabled != false, - title = { Text(stringResource(R.string.enhanced_mode)) }, - summary = { Text(stringResource(configuration.dns.enhancedMode.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "dnsNameServer", contentType = "EditTextListPreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.name_server)) }, - summary = { Text(configuration.dns.nameServer.listSummary(R.string.dont_modify)) }, - enabled = dnsEnabled != false, - onClick = { - onOpenEditableTextList( - R.string.name_server, - configuration.dns.nameServer, - actions::updateDnsNameServer, - ) - }, - ) - } - item(key = "dnsFallback", contentType = "EditTextListPreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.fallback)) }, - summary = { Text(configuration.dns.fallback.listSummary(R.string.dont_modify)) }, - enabled = dnsEnabled != false, - onClick = { - onOpenEditableTextList( - R.string.fallback, - configuration.dns.fallback, - actions::updateDnsFallback, - ) - }, - ) - } - item(key = "dnsDefaultServer", contentType = "EditTextListPreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.default_name_server)) }, - summary = { Text(configuration.dns.defaultServer.listSummary(R.string.dont_modify)) }, - enabled = dnsEnabled != false, - onClick = { - onOpenEditableTextList( - R.string.default_name_server, - configuration.dns.defaultServer, - actions::updateDnsDefaultServer, - ) - }, - ) - } - item(key = "dnsFakeIpFilter", contentType = "EditTextListPreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.fakeip_filter)) }, - summary = { Text(configuration.dns.fakeIpFilter.listSummary(R.string.dont_modify)) }, - enabled = dnsEnabled != false, - onClick = { - onOpenEditableTextList( - R.string.fakeip_filter, - configuration.dns.fakeIpFilter, - actions::updateDnsFakeIpFilter, - ) - }, - ) - } - item(key = "dnsFakeIpFilterMode", contentType = "ListPreference") { - ListPreference( - value = configuration.dns.fakeIPFilterMode, - onValueChange = actions::updateDnsFakeIpFilterMode, - values = ConfigurationOverride.FilterMode.entries, - modifier = Modifier.fillMaxWidth(), - enabled = dnsEnabled != false, - title = { Text(stringResource(R.string.fakeip_filter_mode)) }, - summary = { Text(stringResource(configuration.dns.fakeIPFilterMode.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "dnsGeoIpFallback", contentType = "ListPreference") { - ListPreference( - value = configuration.dns.fallbackFilter.geoIp, - onValueChange = actions::updateDnsGeoIpFallback, - values = booleanOptions, - modifier = Modifier.fillMaxWidth(), - enabled = dnsEnabled != false, - title = { Text(stringResource(R.string.geoip_fallback)) }, - summary = { Text(stringResource(configuration.dns.fallbackFilter.geoIp.textRes)) }, - valueToText = { AnnotatedString(stringResource(it.textRes)) }, - ) - } - item(key = "dnsGeoIpCode", contentType = "EditTextPreference") { - OverrideEditTextPreferenceItem( - title = R.string.geoip_fallback_code, - placeholder = R.string.dont_modify, - emptyLabel = R.string.raw_cn, - value = configuration.dns.fallbackFilter.geoIpCode, - onValueChange = actions::updateDnsGeoIpCode, - enabled = dnsEnabled != false, - ) - } - item(key = "dnsDomainFallback", contentType = "EditTextListPreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.domain_fallback)) }, - summary = { Text(configuration.dns.fallbackFilter.domain.listSummary(R.string.dont_modify)) }, - enabled = dnsEnabled != false, - onClick = { - onOpenEditableTextList( - R.string.domain_fallback, - configuration.dns.fallbackFilter.domain, - actions::updateDnsDomainFallback, - ) - }, - ) - } - item(key = "dnsIpcidrFallback", contentType = "EditTextListPreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.ipcidr_fallback)) }, - summary = { Text(configuration.dns.fallbackFilter.ipcidr.listSummary(R.string.dont_modify)) }, - enabled = dnsEnabled != false, - onClick = { - onOpenEditableTextList( - R.string.ipcidr_fallback, - configuration.dns.fallbackFilter.ipcidr, - actions::updateDnsIpcidrFallback, - ) - }, - ) - } - item(key = "dnsNameserverPolicy", contentType = "EditTextMapPreference") { - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(R.string.name_server_policy)) }, - summary = { Text(configuration.dns.nameserverPolicy.summary(R.string.dont_modify)) }, - enabled = dnsEnabled != false, - onClick = { - onOpenEditableTextMap( - R.string.name_server_policy, - configuration.dns.nameserverPolicy, - actions::updateDnsNameserverPolicy, - ) - }, - ) - } + listPreference( + key = "dnsStrategy", + defaultValue = dnsEnabled, + values = booleanOptions, + rememberState = { rememberCallbackMutableState(dnsEnabled, actions::updateDnsEnable) }, + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.strategy)) }, + summary = { Text(stringResource(it.dnsStrategyTextRes)) }, + valueToText = { AnnotatedString(stringResource(it.dnsStrategyTextRes)) }, + ) + listPreference( + key = "dnsPreferH3", + defaultValue = configuration.dns.preferH3, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState(configuration.dns.preferH3, actions::updateDnsPreferH3) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { dnsEnabled != false }, + title = { Text(stringResource(R.string.prefer_h3)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + OverrideEditTextPreferenceItem( + key = "dnsListen", + title = R.string.listen, + placeholder = R.string.dont_modify, + emptyLabel = R.string.disabled, + value = configuration.dns.listen, + onValueChange = actions::updateDnsListen, + enabled = dnsEnabled != false, + ) + listPreference( + key = "appendSystemDns", + defaultValue = configuration.app.appendSystemDns, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState( + configuration.app.appendSystemDns, + actions::updateAppendSystemDns, + ) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { dnsEnabled != false }, + title = { Text(stringResource(R.string.append_system_dns)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + listPreference( + key = "dnsIpv6", + defaultValue = configuration.dns.ipv6, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState(configuration.dns.ipv6, actions::updateDnsIpv6) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { dnsEnabled != false }, + title = { Text(stringResource(R.string.ipv6)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + listPreference( + key = "dnsUseHosts", + defaultValue = configuration.dns.useHosts, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState(configuration.dns.useHosts, actions::updateDnsUseHosts) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { dnsEnabled != false }, + title = { Text(stringResource(R.string.use_hosts)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + listPreference( + key = "dnsEnhancedMode", + defaultValue = configuration.dns.enhancedMode, + values = ConfigurationOverride.DnsEnhancedMode.entries, + rememberState = { + rememberCallbackMutableState(configuration.dns.enhancedMode, actions::updateDnsEnhancedMode) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { dnsEnabled != false }, + title = { Text(stringResource(R.string.enhanced_mode)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + preference( + key = "dnsNameServer", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.name_server)) }, + summary = { Text(configuration.dns.nameServer.listSummary(R.string.dont_modify)) }, + enabled = dnsEnabled != false, + onClick = { + onOpenEditableTextList( + R.string.name_server, + configuration.dns.nameServer, + actions::updateDnsNameServer, + ) + }, + ) + preference( + key = "dnsFallback", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.fallback)) }, + summary = { Text(configuration.dns.fallback.listSummary(R.string.dont_modify)) }, + enabled = dnsEnabled != false, + onClick = { + onOpenEditableTextList( + R.string.fallback, + configuration.dns.fallback, + actions::updateDnsFallback, + ) + }, + ) + preference( + key = "dnsDefaultServer", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.default_name_server)) }, + summary = { Text(configuration.dns.defaultServer.listSummary(R.string.dont_modify)) }, + enabled = dnsEnabled != false, + onClick = { + onOpenEditableTextList( + R.string.default_name_server, + configuration.dns.defaultServer, + actions::updateDnsDefaultServer, + ) + }, + ) + preference( + key = "dnsFakeIpFilter", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.fakeip_filter)) }, + summary = { Text(configuration.dns.fakeIpFilter.listSummary(R.string.dont_modify)) }, + enabled = dnsEnabled != false, + onClick = { + onOpenEditableTextList( + R.string.fakeip_filter, + configuration.dns.fakeIpFilter, + actions::updateDnsFakeIpFilter, + ) + }, + ) + listPreference( + key = "dnsFakeIpFilterMode", + defaultValue = configuration.dns.fakeIPFilterMode, + values = ConfigurationOverride.FilterMode.entries, + rememberState = { + rememberCallbackMutableState( + configuration.dns.fakeIPFilterMode, + actions::updateDnsFakeIpFilterMode, + ) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { dnsEnabled != false }, + title = { Text(stringResource(R.string.fakeip_filter_mode)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + listPreference( + key = "dnsGeoIpFallback", + defaultValue = configuration.dns.fallbackFilter.geoIp, + values = booleanOptions, + rememberState = { + rememberCallbackMutableState( + configuration.dns.fallbackFilter.geoIp, + actions::updateDnsGeoIpFallback, + ) + }, + modifier = Modifier.fillMaxWidth(), + enabled = { dnsEnabled != false }, + title = { Text(stringResource(R.string.geoip_fallback)) }, + summary = { Text(stringResource(it.textRes)) }, + valueToText = { AnnotatedString(stringResource(it.textRes)) }, + ) + OverrideEditTextPreferenceItem( + key = "dnsGeoIpCode", + title = R.string.geoip_fallback_code, + placeholder = R.string.dont_modify, + emptyLabel = R.string.raw_cn, + value = configuration.dns.fallbackFilter.geoIpCode, + onValueChange = actions::updateDnsGeoIpCode, + enabled = dnsEnabled != false, + ) + preference( + key = "dnsDomainFallback", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.domain_fallback)) }, + summary = { Text(configuration.dns.fallbackFilter.domain.listSummary(R.string.dont_modify)) }, + enabled = dnsEnabled != false, + onClick = { + onOpenEditableTextList( + R.string.domain_fallback, + configuration.dns.fallbackFilter.domain, + actions::updateDnsDomainFallback, + ) + }, + ) + preference( + key = "dnsIpcidrFallback", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.ipcidr_fallback)) }, + summary = { Text(configuration.dns.fallbackFilter.ipcidr.listSummary(R.string.dont_modify)) }, + enabled = dnsEnabled != false, + onClick = { + onOpenEditableTextList( + R.string.ipcidr_fallback, + configuration.dns.fallbackFilter.ipcidr, + actions::updateDnsIpcidrFallback, + ) + }, + ) + preference( + key = "dnsNameserverPolicy", + modifier = Modifier.fillMaxWidth(), + title = { Text(stringResource(R.string.name_server_policy)) }, + summary = { Text(configuration.dns.nameserverPolicy.summary(R.string.dont_modify)) }, + enabled = dnsEnabled != false, + onClick = { + onOpenEditableTextMap( + R.string.name_server_policy, + configuration.dns.nameserverPolicy, + actions::updateDnsNameserverPolicy, + ) + }, + ) } -@Composable -private fun OverrideEditTextPreferenceItem( +private fun LazyListScope.OverrideEditTextPreferenceItem( + key: String, @StringRes title: Int, @StringRes placeholder: Int, @StringRes emptyLabel: Int, @@ -631,80 +628,82 @@ private fun OverrideEditTextPreferenceItem( enabled: Boolean = true, numericOnly: Boolean = false, ) { - var showDialog by remember { mutableStateOf(false) } - val summary = - when { - value == null -> stringResource(placeholder) - value.isEmpty() -> stringResource(emptyLabel) - else -> value - } - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(title)) }, - summary = { Text(summary) }, - enabled = enabled, - onClick = { showDialog = true }, - ) - if (showDialog) { - var inputText by - remember(value) { - mutableStateOf( - TextFieldValue(text = value.orEmpty(), selection = TextRange(value.orEmpty().length)) - ) + item(key = key, contentType = "EditTextPreference") { + var showDialog by remember { mutableStateOf(false) } + val summary = + when { + value == null -> stringResource(placeholder) + value.isEmpty() -> stringResource(emptyLabel) + else -> value } - val focusRequester = remember { FocusRequester() } - val keyboardController = LocalSoftwareKeyboardController.current - LaunchedEffect(Unit) { - focusRequester.requestFocus() - keyboardController?.show() - } - AlertDialog( - onDismissRequest = { showDialog = false }, + Preference( + modifier = Modifier.fillMaxWidth(), title = { Text(stringResource(title)) }, - text = { - OutlinedTextField( - value = inputText, - onValueChange = { inputText = if (numericOnly) it.filterDigits() else it }, - keyboardOptions = - if (numericOnly) { - KeyboardOptions(keyboardType = KeyboardType.Number) - } else { - KeyboardOptions.Default - }, - singleLine = true, - modifier = Modifier.fillMaxWidth().focusRequester(focusRequester), - ) - }, - confirmButton = { - TextButton( - onClick = { - onValueChange( + summary = { Text(summary) }, + enabled = enabled, + onClick = { showDialog = true }, + ) + if (showDialog) { + var inputText by + remember(value) { + mutableStateOf( + TextFieldValue(text = value.orEmpty(), selection = TextRange(value.orEmpty().length)) + ) + } + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + LaunchedEffect(Unit) { + focusRequester.requestFocus() + keyboardController?.show() + } + AlertDialog( + onDismissRequest = { showDialog = false }, + title = { Text(stringResource(title)) }, + text = { + OutlinedTextField( + value = inputText, + onValueChange = { inputText = if (numericOnly) it.filterDigits() else it }, + keyboardOptions = if (numericOnly) { - portText(parsePort(inputText.text)) + KeyboardOptions(keyboardType = KeyboardType.Number) } else { - inputText.text - } - ) - showDialog = false - } - ) { - Text(stringResource(R.string.ok)) - } - }, - dismissButton = { - Row { + KeyboardOptions.Default + }, + singleLine = true, + modifier = Modifier.fillMaxWidth().focusRequester(focusRequester), + ) + }, + confirmButton = { TextButton( onClick = { - onValueChange(null) + onValueChange( + if (numericOnly) { + portText(parsePort(inputText.text)) + } else { + inputText.text + } + ) showDialog = false } ) { - Text(stringResource(R.string.reset)) + Text(stringResource(R.string.ok)) } - TextButton(onClick = { showDialog = false }) { Text(stringResource(R.string.cancel)) } - } - }, - ) + }, + dismissButton = { + Row { + TextButton( + onClick = { + onValueChange(null) + showDialog = false + } + ) { + Text(stringResource(R.string.reset)) + } + TextButton(onClick = { showDialog = false }) { Text(stringResource(R.string.cancel)) } + } + }, + ) + } } } diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/SettingsPreference.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/SettingsPreference.kt index b3c47b8582..88938ec2ee 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/SettingsPreference.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/SettingsPreference.kt @@ -5,6 +5,8 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -29,3 +31,20 @@ fun List?.listSummary(@StringRes placeholder: Int) = fun initialTextFieldValue(text: String) = TextFieldValue(text = text, selection = TextRange(text.length)) + +@Composable +fun rememberCallbackMutableState(value: T, onValueChange: (T) -> Unit): MutableState { + return remember(value, onValueChange) { + object : MutableState { + override var value: T = value + set(newValue) { + field = newValue + onValueChange(newValue) + } + + override fun component1(): T = value + + override fun component2(): (T) -> Unit = { this.value = it } + } + } +} From 1006e832eb6afa5ddb8e9099906937e3ae3d8344 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 29 Apr 2026 16:43:26 +0800 Subject: [PATCH 2/4] Replace extensions --- .../clash/settings/ui/AppSettingsScreen.kt | 32 ++--- .../settings/ui/MetaFeatureSettingsScreen.kt | 119 ++++++----------- .../settings/ui/NetworkSettingsScreen.kt | 64 ++++----- .../settings/ui/OverrideSettingsScreen.kt | 126 +++++++----------- .../clash/settings/ui/PreferenceExtensions.kt | 63 +++++++++ .../clash/settings/ui/SettingsPreference.kt | 19 --- 6 files changed, 192 insertions(+), 231 deletions(-) create mode 100644 app/src/main/kotlin/com/github/kr328/clash/settings/ui/PreferenceExtensions.kt diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/AppSettingsScreen.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/AppSettingsScreen.kt index c78600e229..8b45f4583e 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/AppSettingsScreen.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/AppSettingsScreen.kt @@ -71,10 +71,8 @@ private fun AppSettingsContent( ) switchPreference( key = "auto_restart", - defaultValue = uiState.autoRestart, - rememberState = { - rememberCallbackMutableState(uiState.autoRestart, onAutoRestartChange) - }, + value = uiState.autoRestart, + onValueChange = onAutoRestartChange, icon = { Icon(imageVector = MihomoIcons.BaselineRestore, contentDescription = null) }, title = { Text(stringResource(R.string.auto_restart)) }, summary = { Text(stringResource(R.string.allow_clash_auto_restart)) }, @@ -86,30 +84,26 @@ private fun AppSettingsContent( ) listPreference( key = "dark_mode", - defaultValue = uiState.darkMode, + value = uiState.darkMode, + onValueChange = onDarkModeChange, values = listOf(DarkMode.Auto, DarkMode.ForceLight, DarkMode.ForceDark), - rememberState = { rememberCallbackMutableState(uiState.darkMode, onDarkModeChange) }, icon = { Icon(imageVector = MihomoIcons.BaselineBrightness4, contentDescription = null) }, title = { Text(stringResource(R.string.dark_mode)) }, - summary = { Text(stringResource(it.summaryRes)) }, + summary = { Text(stringResource(uiState.darkMode.summaryRes)) }, valueToText = { androidx.compose.ui.text.AnnotatedString(stringResource(it.summaryRes)) }, ) switchPreference( key = "hide_app_icon", - defaultValue = uiState.hideAppIcon, - rememberState = { - rememberCallbackMutableState(uiState.hideAppIcon, onHideAppIconChange) - }, + value = uiState.hideAppIcon, + onValueChange = onHideAppIconChange, icon = { Icon(imageVector = MihomoIcons.BaselineHide, contentDescription = null) }, title = { Text(stringResource(R.string.hide_app_icon_title)) }, summary = { Text(stringResource(R.string.hide_app_icon_desc)) }, ) switchPreference( key = "hide_from_recents", - defaultValue = uiState.hideFromRecents, - rememberState = { - rememberCallbackMutableState(uiState.hideFromRecents, onHideFromRecentsChange) - }, + value = uiState.hideFromRecents, + onValueChange = onHideFromRecentsChange, icon = { Icon(imageVector = MihomoIcons.BaselineStack, contentDescription = null) }, title = { Text(stringResource(R.string.hide_from_recents_title)) }, summary = { Text(stringResource(R.string.hide_from_recents_desc)) }, @@ -118,11 +112,9 @@ private fun AppSettingsContent( preferenceCategory(key = "cat_service", title = { Text(stringResource(R.string.service)) }) switchPreference( key = "show_traffic", - defaultValue = uiState.dynamicNotification, - rememberState = { - rememberCallbackMutableState(uiState.dynamicNotification, onDynamicNotificationChange) - }, - enabled = { !clashRunning }, + value = uiState.dynamicNotification, + onValueChange = onDynamicNotificationChange, + enabled = !clashRunning, icon = { Icon(imageVector = MihomoIcons.BaselineDomain, contentDescription = null) }, title = { Text(stringResource(R.string.show_traffic)) }, summary = { Text(stringResource(R.string.show_traffic_summary)) }, diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/MetaFeatureSettingsScreen.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/MetaFeatureSettingsScreen.kt index a0fffbf01d..ff023a8b4a 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/MetaFeatureSettingsScreen.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/MetaFeatureSettingsScreen.kt @@ -249,50 +249,42 @@ private fun LazyListScope.metaBasicPreferenceItems( listPreference( key = "unifiedDelay", - defaultValue = configuration.unifiedDelay, + value = configuration.unifiedDelay, + onValueChange = actions::updateUnifiedDelay, values = booleanOptions, - rememberState = { - rememberCallbackMutableState(configuration.unifiedDelay, actions::updateUnifiedDelay) - }, modifier = Modifier.fillMaxWidth(), title = { Text(stringResource(R.string.unified_delay)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.unifiedDelay.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) listPreference( key = "geodataMode", - defaultValue = configuration.geodataMode, + value = configuration.geodataMode, + onValueChange = actions::updateGeodataMode, values = booleanOptions, - rememberState = { - rememberCallbackMutableState(configuration.geodataMode, actions::updateGeodataMode) - }, modifier = Modifier.fillMaxWidth(), title = { Text(stringResource(R.string.geodata_mode)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.geodataMode.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) listPreference( key = "tcpConcurrent", - defaultValue = configuration.tcpConcurrent, + value = configuration.tcpConcurrent, + onValueChange = actions::updateTcpConcurrent, values = booleanOptions, - rememberState = { - rememberCallbackMutableState(configuration.tcpConcurrent, actions::updateTcpConcurrent) - }, modifier = Modifier.fillMaxWidth(), title = { Text(stringResource(R.string.tcp_concurrent)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.tcpConcurrent.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) listPreference( key = "findProcessMode", - defaultValue = configuration.findProcessMode, + value = configuration.findProcessMode, + onValueChange = actions::updateFindProcessMode, values = ConfigurationOverride.FindProcessMode.entries, - rememberState = { - rememberCallbackMutableState(configuration.findProcessMode, actions::updateFindProcessMode) - }, modifier = Modifier.fillMaxWidth(), title = { Text(stringResource(R.string.find_process_mode)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.findProcessMode.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) } @@ -308,14 +300,12 @@ private fun LazyListScope.metaSnifferPreferenceItems( ) listPreference( key = "snifferEnable", - defaultValue = configuration.sniffer.enable, + value = configuration.sniffer.enable, + onValueChange = actions::updateSnifferEnable, values = booleanOptions, - rememberState = { - rememberCallbackMutableState(configuration.sniffer.enable, actions::updateSnifferEnable) - }, modifier = Modifier.fillMaxWidth(), title = { Text(stringResource(R.string.strategy)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.sniffer.enable.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) preference( @@ -334,18 +324,15 @@ private fun LazyListScope.metaSnifferPreferenceItems( ) listPreference( key = "sniffHttpOverrideDestination", - defaultValue = configuration.sniffer.sniff.http.overrideDestination, + value = configuration.sniffer.sniff.http.overrideDestination, + onValueChange = actions::updateSniffHttpOverrideDestination, values = booleanOptions, - rememberState = { - rememberCallbackMutableState( - configuration.sniffer.sniff.http.overrideDestination, - actions::updateSniffHttpOverrideDestination, - ) - }, modifier = Modifier.fillMaxWidth(), - enabled = { configuration.sniffer.enable != false }, + enabled = configuration.sniffer.enable != false, title = { Text(stringResource(R.string.sniff_http_override_destination)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { + Text(stringResource(configuration.sniffer.sniff.http.overrideDestination.textRes)) + }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) preference( @@ -364,18 +351,13 @@ private fun LazyListScope.metaSnifferPreferenceItems( ) listPreference( key = "sniffTlsOverrideDestination", - defaultValue = configuration.sniffer.sniff.tls.overrideDestination, + value = configuration.sniffer.sniff.tls.overrideDestination, + onValueChange = actions::updateSniffTlsOverrideDestination, values = booleanOptions, - rememberState = { - rememberCallbackMutableState( - configuration.sniffer.sniff.tls.overrideDestination, - actions::updateSniffTlsOverrideDestination, - ) - }, modifier = Modifier.fillMaxWidth(), - enabled = { configuration.sniffer.enable != false }, + enabled = configuration.sniffer.enable != false, title = { Text(stringResource(R.string.sniff_tls_override_destination)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.sniffer.sniff.tls.overrideDestination.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) preference( @@ -394,63 +376,48 @@ private fun LazyListScope.metaSnifferPreferenceItems( ) listPreference( key = "sniffQuicOverrideDestination", - defaultValue = configuration.sniffer.sniff.quic.overrideDestination, + value = configuration.sniffer.sniff.quic.overrideDestination, + onValueChange = actions::updateSniffQuicOverrideDestination, values = booleanOptions, - rememberState = { - rememberCallbackMutableState( - configuration.sniffer.sniff.quic.overrideDestination, - actions::updateSniffQuicOverrideDestination, - ) - }, modifier = Modifier.fillMaxWidth(), - enabled = { configuration.sniffer.enable != false }, + enabled = configuration.sniffer.enable != false, title = { Text(stringResource(R.string.sniff_quic_override_destination)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { + Text(stringResource(configuration.sniffer.sniff.quic.overrideDestination.textRes)) + }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) listPreference( key = "forceDnsMapping", - defaultValue = configuration.sniffer.forceDnsMapping, + value = configuration.sniffer.forceDnsMapping, + onValueChange = actions::updateForceDnsMapping, values = booleanOptions, - rememberState = { - rememberCallbackMutableState( - configuration.sniffer.forceDnsMapping, - actions::updateForceDnsMapping, - ) - }, modifier = Modifier.fillMaxWidth(), - enabled = { configuration.sniffer.enable != false }, + enabled = configuration.sniffer.enable != false, title = { Text(stringResource(R.string.force_dns_mapping)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.sniffer.forceDnsMapping.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) listPreference( key = "parsePureIp", - defaultValue = configuration.sniffer.parsePureIp, + value = configuration.sniffer.parsePureIp, + onValueChange = actions::updateParsePureIp, values = booleanOptions, - rememberState = { - rememberCallbackMutableState(configuration.sniffer.parsePureIp, actions::updateParsePureIp) - }, modifier = Modifier.fillMaxWidth(), - enabled = { configuration.sniffer.enable != false }, + enabled = configuration.sniffer.enable != false, title = { Text(stringResource(R.string.parse_pure_ip)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.sniffer.parsePureIp.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) listPreference( key = "overrideDestination", - defaultValue = configuration.sniffer.overrideDestination, + value = configuration.sniffer.overrideDestination, + onValueChange = actions::updateOverrideDestination, values = booleanOptions, - rememberState = { - rememberCallbackMutableState( - configuration.sniffer.overrideDestination, - actions::updateOverrideDestination, - ) - }, modifier = Modifier.fillMaxWidth(), - enabled = { configuration.sniffer.enable != false }, + enabled = configuration.sniffer.enable != false, title = { Text(stringResource(R.string.override_destination)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.sniffer.overrideDestination.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) preference( diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/NetworkSettingsScreen.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/NetworkSettingsScreen.kt index b40872a6a3..0c375d1005 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/NetworkSettingsScreen.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/NetworkSettingsScreen.kt @@ -97,9 +97,9 @@ private fun NetworkSettingsContent( LazyColumn(modifier = Modifier.fillMaxSize(), contentPadding = innerPadding) { switchPreference( key = "route_system_traffic", - defaultValue = uiState.enableVpn, - rememberState = { rememberCallbackMutableState(uiState.enableVpn, onEnableVpnChange) }, - enabled = { !clashRunning }, + value = uiState.enableVpn, + onValueChange = onEnableVpnChange, + enabled = !clashRunning, icon = { Icon(imageVector = MihomoIcons.BaselineVpnLock, contentDescription = null) }, title = { Text(stringResource(R.string.route_system_traffic)) }, summary = { Text(stringResource(R.string.routing_via_vpn_service)) }, @@ -112,81 +112,69 @@ private fun NetworkSettingsContent( switchPreference( key = "bypass_private_network", - defaultValue = uiState.bypassPrivateNetwork, - rememberState = { - rememberCallbackMutableState(uiState.bypassPrivateNetwork, onBypassPrivateNetworkChange) - }, - enabled = { vpnDependenciesEnabled }, + value = uiState.bypassPrivateNetwork, + onValueChange = onBypassPrivateNetworkChange, + enabled = vpnDependenciesEnabled, title = { Text(stringResource(R.string.bypass_private_network)) }, summary = { Text(stringResource(R.string.bypass_private_network_summary)) }, ) switchPreference( key = "dns_hijacking", - defaultValue = uiState.dnsHijacking, - rememberState = { - rememberCallbackMutableState(uiState.dnsHijacking, onDnsHijackingChange) - }, - enabled = { vpnDependenciesEnabled }, + value = uiState.dnsHijacking, + onValueChange = onDnsHijackingChange, + enabled = vpnDependenciesEnabled, title = { Text(stringResource(R.string.dns_hijacking)) }, summary = { Text(stringResource(R.string.dns_hijacking_summary)) }, ) switchPreference( key = "allow_bypass", - defaultValue = uiState.allowBypass, - rememberState = { - rememberCallbackMutableState(uiState.allowBypass, onAllowBypassChange) - }, - enabled = { vpnDependenciesEnabled }, + value = uiState.allowBypass, + onValueChange = onAllowBypassChange, + enabled = vpnDependenciesEnabled, title = { Text(stringResource(R.string.allow_bypass)) }, summary = { Text(stringResource(R.string.allow_bypass_summary)) }, ) switchPreference( key = "allow_ipv6", - defaultValue = uiState.allowIpv6, - rememberState = { rememberCallbackMutableState(uiState.allowIpv6, onAllowIpv6Change) }, - enabled = { vpnDependenciesEnabled }, + value = uiState.allowIpv6, + onValueChange = onAllowIpv6Change, + enabled = vpnDependenciesEnabled, title = { Text(stringResource(R.string.allow_ipv6)) }, summary = { Text(stringResource(R.string.allow_ipv6_summary)) }, ) if (uiState.hasSystemProxyOption) { switchPreference( key = "system_proxy", - defaultValue = uiState.systemProxy, - rememberState = { - rememberCallbackMutableState(uiState.systemProxy, onSystemProxyChange) - }, - enabled = { vpnDependenciesEnabled }, + value = uiState.systemProxy, + onValueChange = onSystemProxyChange, + enabled = vpnDependenciesEnabled, title = { Text(stringResource(R.string.system_proxy)) }, summary = { Text(stringResource(R.string.system_proxy_summary)) }, ) } listPreference( key = "tun_stack_mode", - defaultValue = tunStackMode, + value = tunStackMode, + onValueChange = { onTunStackModeChange(it.persistedValue) }, values = TunStackMode.entries, - rememberState = { - rememberCallbackMutableState(tunStackMode) { onTunStackModeChange(it.persistedValue) } - }, - enabled = { vpnDependenciesEnabled }, + enabled = vpnDependenciesEnabled, title = { Text(stringResource(R.string.tun_stack_mode)) }, - summary = { Text(stringResource(it.summaryRes)) }, + summary = { Text(stringResource(tunStackMode.summaryRes)) }, valueToText = { androidx.compose.ui.text.AnnotatedString(stringResource(it.summaryRes)) }, ) listPreference( key = "access_control_mode", - defaultValue = uiState.accessControlMode, + value = uiState.accessControlMode, + onValueChange = onAccessControlModeChange, values = listOf( AccessControlMode.AcceptAll, AccessControlMode.AcceptSelected, AccessControlMode.DenySelected, ), - rememberState = { - rememberCallbackMutableState(uiState.accessControlMode, onAccessControlModeChange) - }, - enabled = { vpnDependenciesEnabled }, + enabled = vpnDependenciesEnabled, title = { Text(stringResource(R.string.access_control_mode)) }, - summary = { Text(stringResource(it.summaryRes)) }, + summary = { Text(stringResource(uiState.accessControlMode.summaryRes)) }, valueToText = { androidx.compose.ui.text.AnnotatedString(stringResource(it.summaryRes)) }, ) preference( diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt index d989ee1b99..fd647dbc6e 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt @@ -269,24 +269,22 @@ private fun LazyListScope.generalPreferenceItems( ) listPreference( key = "allowLan", - defaultValue = configuration.allowLan, + value = configuration.allowLan, + onValueChange = actions::updateAllowLan, values = booleanOptions, - rememberState = { - rememberCallbackMutableState(configuration.allowLan, actions::updateAllowLan) - }, modifier = Modifier.fillMaxWidth(), title = { Text(stringResource(R.string.allow_lan)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.allowLan.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) listPreference( key = "ipv6", - defaultValue = configuration.ipv6, + value = configuration.ipv6, + onValueChange = actions::updateIpv6, values = booleanOptions, - rememberState = { rememberCallbackMutableState(configuration.ipv6, actions::updateIpv6) }, modifier = Modifier.fillMaxWidth(), title = { Text(stringResource(R.string.ipv6)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.ipv6.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) OverrideEditTextPreferenceItem( @@ -330,17 +328,14 @@ private fun LazyListScope.generalPreferenceItems( ) listPreference( key = "allowPrivateNetwork", - defaultValue = configuration.externalControllerCors.allowPrivateNetwork, + value = configuration.externalControllerCors.allowPrivateNetwork, + onValueChange = actions::updateAllowPrivateNetwork, values = booleanOptions, - rememberState = { - rememberCallbackMutableState( - configuration.externalControllerCors.allowPrivateNetwork, - actions::updateAllowPrivateNetwork, - ) - }, modifier = Modifier.fillMaxWidth(), title = { Text(stringResource(R.string.allow_private_network)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { + Text(stringResource(configuration.externalControllerCors.allowPrivateNetwork.textRes)) + }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) OverrideEditTextPreferenceItem( @@ -353,24 +348,22 @@ private fun LazyListScope.generalPreferenceItems( ) listPreference( key = "mode", - defaultValue = configuration.mode, + value = configuration.mode, + onValueChange = actions::updateMode, values = TunnelState.Mode.entries, - rememberState = { rememberCallbackMutableState(configuration.mode, actions::updateMode) }, modifier = Modifier.fillMaxWidth(), title = { Text(stringResource(R.string.mode)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.mode.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) listPreference( key = "logLevel", - defaultValue = configuration.logLevel, + value = configuration.logLevel, + onValueChange = actions::updateLogLevel, values = LogMessage.Level.entries, - rememberState = { - rememberCallbackMutableState(configuration.logLevel, actions::updateLogLevel) - }, modifier = Modifier.fillMaxWidth(), title = { Text(stringResource(R.string.log_level)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.logLevel.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) preference( @@ -392,25 +385,23 @@ private fun LazyListScope.dnsPreferenceItems( preferenceCategory(key = "cat_dns", title = { Text(stringResource(R.string.dns)) }) listPreference( key = "dnsStrategy", - defaultValue = dnsEnabled, + value = dnsEnabled, + onValueChange = actions::updateDnsEnable, values = booleanOptions, - rememberState = { rememberCallbackMutableState(dnsEnabled, actions::updateDnsEnable) }, modifier = Modifier.fillMaxWidth(), title = { Text(stringResource(R.string.strategy)) }, - summary = { Text(stringResource(it.dnsStrategyTextRes)) }, + summary = { Text(stringResource(dnsEnabled.dnsStrategyTextRes)) }, valueToText = { AnnotatedString(stringResource(it.dnsStrategyTextRes)) }, ) listPreference( key = "dnsPreferH3", - defaultValue = configuration.dns.preferH3, + value = configuration.dns.preferH3, + onValueChange = actions::updateDnsPreferH3, values = booleanOptions, - rememberState = { - rememberCallbackMutableState(configuration.dns.preferH3, actions::updateDnsPreferH3) - }, modifier = Modifier.fillMaxWidth(), - enabled = { dnsEnabled != false }, + enabled = dnsEnabled != false, title = { Text(stringResource(R.string.prefer_h3)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.dns.preferH3.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) OverrideEditTextPreferenceItem( @@ -424,57 +415,46 @@ private fun LazyListScope.dnsPreferenceItems( ) listPreference( key = "appendSystemDns", - defaultValue = configuration.app.appendSystemDns, + value = configuration.app.appendSystemDns, + onValueChange = actions::updateAppendSystemDns, values = booleanOptions, - rememberState = { - rememberCallbackMutableState( - configuration.app.appendSystemDns, - actions::updateAppendSystemDns, - ) - }, modifier = Modifier.fillMaxWidth(), - enabled = { dnsEnabled != false }, + enabled = dnsEnabled != false, title = { Text(stringResource(R.string.append_system_dns)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.app.appendSystemDns.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) listPreference( key = "dnsIpv6", - defaultValue = configuration.dns.ipv6, + value = configuration.dns.ipv6, + onValueChange = actions::updateDnsIpv6, values = booleanOptions, - rememberState = { - rememberCallbackMutableState(configuration.dns.ipv6, actions::updateDnsIpv6) - }, modifier = Modifier.fillMaxWidth(), - enabled = { dnsEnabled != false }, + enabled = dnsEnabled != false, title = { Text(stringResource(R.string.ipv6)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.dns.ipv6.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) listPreference( key = "dnsUseHosts", - defaultValue = configuration.dns.useHosts, + value = configuration.dns.useHosts, + onValueChange = actions::updateDnsUseHosts, values = booleanOptions, - rememberState = { - rememberCallbackMutableState(configuration.dns.useHosts, actions::updateDnsUseHosts) - }, modifier = Modifier.fillMaxWidth(), - enabled = { dnsEnabled != false }, + enabled = dnsEnabled != false, title = { Text(stringResource(R.string.use_hosts)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.dns.useHosts.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) listPreference( key = "dnsEnhancedMode", - defaultValue = configuration.dns.enhancedMode, + value = configuration.dns.enhancedMode, + onValueChange = actions::updateDnsEnhancedMode, values = ConfigurationOverride.DnsEnhancedMode.entries, - rememberState = { - rememberCallbackMutableState(configuration.dns.enhancedMode, actions::updateDnsEnhancedMode) - }, modifier = Modifier.fillMaxWidth(), - enabled = { dnsEnabled != false }, + enabled = dnsEnabled != false, title = { Text(stringResource(R.string.enhanced_mode)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.dns.enhancedMode.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) preference( @@ -535,34 +515,24 @@ private fun LazyListScope.dnsPreferenceItems( ) listPreference( key = "dnsFakeIpFilterMode", - defaultValue = configuration.dns.fakeIPFilterMode, + value = configuration.dns.fakeIPFilterMode, + onValueChange = actions::updateDnsFakeIpFilterMode, values = ConfigurationOverride.FilterMode.entries, - rememberState = { - rememberCallbackMutableState( - configuration.dns.fakeIPFilterMode, - actions::updateDnsFakeIpFilterMode, - ) - }, modifier = Modifier.fillMaxWidth(), - enabled = { dnsEnabled != false }, + enabled = dnsEnabled != false, title = { Text(stringResource(R.string.fakeip_filter_mode)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.dns.fakeIPFilterMode.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) listPreference( key = "dnsGeoIpFallback", - defaultValue = configuration.dns.fallbackFilter.geoIp, + value = configuration.dns.fallbackFilter.geoIp, + onValueChange = actions::updateDnsGeoIpFallback, values = booleanOptions, - rememberState = { - rememberCallbackMutableState( - configuration.dns.fallbackFilter.geoIp, - actions::updateDnsGeoIpFallback, - ) - }, modifier = Modifier.fillMaxWidth(), - enabled = { dnsEnabled != false }, + enabled = dnsEnabled != false, title = { Text(stringResource(R.string.geoip_fallback)) }, - summary = { Text(stringResource(it.textRes)) }, + summary = { Text(stringResource(configuration.dns.fallbackFilter.geoIp.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) OverrideEditTextPreferenceItem( diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/PreferenceExtensions.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/PreferenceExtensions.kt new file mode 100644 index 0000000000..b0859d26df --- /dev/null +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/PreferenceExtensions.kt @@ -0,0 +1,63 @@ +@file:Suppress( + "PackageDirectoryMismatch", + "NOTHING_TO_INLINE", +) // TODO: https://github.com/zhanghai/ComposePreference/pull/34 + +package me.zhanghai.compose.preference + +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.AnnotatedString + +inline fun LazyListScope.listPreference( + key: String, + value: T, + noinline onValueChange: (T) -> Unit, + values: List, + noinline title: @Composable () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + noinline icon: @Composable (() -> Unit)? = null, + noinline summary: @Composable (() -> Unit)? = null, + type: ListPreferenceType = ListPreferenceType.ALERT_DIALOG, + noinline valueToText: @Composable (T) -> AnnotatedString = { AnnotatedString(it.toString()) }, +) { + item(key = key, contentType = "ListPreference") { + ListPreference( + value = value, + onValueChange = onValueChange, + values = values, + title = title, + modifier = modifier, + enabled = enabled, + icon = icon, + summary = summary, + type = type, + valueToText = valueToText, + ) + } +} + +inline fun LazyListScope.switchPreference( + key: String, + value: Boolean, + noinline onValueChange: (Boolean) -> Unit, + noinline title: @Composable () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + noinline icon: @Composable (() -> Unit)? = null, + noinline summary: @Composable (() -> Unit)? = null, +) { + item(key = key, contentType = "SwitchPreference") { + SwitchPreference( + value = value, + onValueChange = onValueChange, + title = title, + modifier = modifier, + enabled = enabled, + icon = icon, + summary = summary, + ) + } +} diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/SettingsPreference.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/SettingsPreference.kt index 88938ec2ee..b3c47b8582 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/SettingsPreference.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/SettingsPreference.kt @@ -5,8 +5,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -31,20 +29,3 @@ fun List?.listSummary(@StringRes placeholder: Int) = fun initialTextFieldValue(text: String) = TextFieldValue(text = text, selection = TextRange(text.length)) - -@Composable -fun rememberCallbackMutableState(value: T, onValueChange: (T) -> Unit): MutableState { - return remember(value, onValueChange) { - object : MutableState { - override var value: T = value - set(newValue) { - field = newValue - onValueChange(newValue) - } - - override fun component1(): T = value - - override fun component2(): (T) -> Unit = { this.value = it } - } - } -} From 34a7d06ea6069b33c25770958561bc95f2d09aae Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 29 Apr 2026 16:51:54 +0800 Subject: [PATCH 3/4] Rename --- .../settings/ui/OverrideSettingsScreen.kt | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt index fd647dbc6e..c2db7601c9 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt @@ -209,7 +209,7 @@ private fun LazyListScope.generalPreferenceItems( onOpenEditableTextList: (Int, List?, (List?) -> Unit) -> Unit, ) { preferenceCategory(key = "cat_general", title = { Text(stringResource(R.string.general)) }) - OverrideEditTextPreferenceItem( + overrideEditTextPreferenceItem( key = "httpPort", title = R.string.http_port, placeholder = R.string.dont_modify, @@ -218,7 +218,7 @@ private fun LazyListScope.generalPreferenceItems( onValueChange = { actions.updateHttpPort(parsePort(it)) }, numericOnly = true, ) - OverrideEditTextPreferenceItem( + overrideEditTextPreferenceItem( key = "socksPort", title = R.string.socks_port, placeholder = R.string.dont_modify, @@ -227,7 +227,7 @@ private fun LazyListScope.generalPreferenceItems( onValueChange = { actions.updateSocksPort(parsePort(it)) }, numericOnly = true, ) - OverrideEditTextPreferenceItem( + overrideEditTextPreferenceItem( key = "redirectPort", title = R.string.redirect_port, placeholder = R.string.dont_modify, @@ -236,7 +236,7 @@ private fun LazyListScope.generalPreferenceItems( onValueChange = { actions.updateRedirectPort(parsePort(it)) }, numericOnly = true, ) - OverrideEditTextPreferenceItem( + overrideEditTextPreferenceItem( key = "tproxyPort", title = R.string.tproxy_port, placeholder = R.string.dont_modify, @@ -245,7 +245,7 @@ private fun LazyListScope.generalPreferenceItems( onValueChange = { actions.updateTproxyPort(parsePort(it)) }, numericOnly = true, ) - OverrideEditTextPreferenceItem( + overrideEditTextPreferenceItem( key = "mixedPort", title = R.string.mixed_port, placeholder = R.string.dont_modify, @@ -287,7 +287,7 @@ private fun LazyListScope.generalPreferenceItems( summary = { Text(stringResource(configuration.ipv6.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) - OverrideEditTextPreferenceItem( + overrideEditTextPreferenceItem( key = "bindAddress", title = R.string.bind_address, placeholder = R.string.dont_modify, @@ -295,7 +295,7 @@ private fun LazyListScope.generalPreferenceItems( value = configuration.bindAddress, onValueChange = actions::updateBindAddress, ) - OverrideEditTextPreferenceItem( + overrideEditTextPreferenceItem( key = "externalController", title = R.string.external_controller, placeholder = R.string.dont_modify, @@ -303,7 +303,7 @@ private fun LazyListScope.generalPreferenceItems( value = configuration.externalController, onValueChange = actions::updateExternalController, ) - OverrideEditTextPreferenceItem( + overrideEditTextPreferenceItem( key = "externalControllerTls", title = R.string.external_controller_tls, placeholder = R.string.dont_modify, @@ -338,7 +338,7 @@ private fun LazyListScope.generalPreferenceItems( }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) - OverrideEditTextPreferenceItem( + overrideEditTextPreferenceItem( key = "secret", title = R.string.secret, placeholder = R.string.dont_modify, @@ -404,7 +404,7 @@ private fun LazyListScope.dnsPreferenceItems( summary = { Text(stringResource(configuration.dns.preferH3.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) - OverrideEditTextPreferenceItem( + overrideEditTextPreferenceItem( key = "dnsListen", title = R.string.listen, placeholder = R.string.dont_modify, @@ -535,7 +535,7 @@ private fun LazyListScope.dnsPreferenceItems( summary = { Text(stringResource(configuration.dns.fallbackFilter.geoIp.textRes)) }, valueToText = { AnnotatedString(stringResource(it.textRes)) }, ) - OverrideEditTextPreferenceItem( + overrideEditTextPreferenceItem( key = "dnsGeoIpCode", title = R.string.geoip_fallback_code, placeholder = R.string.dont_modify, @@ -588,7 +588,7 @@ private fun LazyListScope.dnsPreferenceItems( ) } -private fun LazyListScope.OverrideEditTextPreferenceItem( +private fun LazyListScope.overrideEditTextPreferenceItem( key: String, @StringRes title: Int, @StringRes placeholder: Int, From 915356cd570624a406427fabd9737a661d621127 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 29 Apr 2026 16:53:09 +0800 Subject: [PATCH 4/4] Reformat --- .../github/kr328/clash/settings/ui/PreferenceExtensions.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/PreferenceExtensions.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/PreferenceExtensions.kt index b0859d26df..51cbefe174 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/PreferenceExtensions.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/PreferenceExtensions.kt @@ -1,7 +1,5 @@ -@file:Suppress( - "PackageDirectoryMismatch", - "NOTHING_TO_INLINE", -) // TODO: https://github.com/zhanghai/ComposePreference/pull/34 +// TODO: https://github.com/zhanghai/ComposePreference/pull/34 +@file:Suppress("PackageDirectoryMismatch", "NOTHING_TO_INLINE") package me.zhanghai.compose.preference