diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/OverrideSettingsActivity.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/OverrideSettingsActivity.kt index 01cae19bcb..2ac263097b 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/OverrideSettingsActivity.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/OverrideSettingsActivity.kt @@ -1,35 +1,15 @@ package com.github.kr328.clash.settings -import com.github.kr328.clash.core.Clash -import com.github.kr328.clash.settings.ui.OverrideSettingsDesign -import com.github.kr328.clash.ui.DesignActivity -import com.github.kr328.clash.util.withClash -import kotlinx.coroutines.isActive -import kotlinx.coroutines.selects.select +import android.os.Bundle +import androidx.activity.compose.setContent +import com.github.kr328.clash.settings.ui.OverrideSettingsScreen +import com.github.kr328.clash.ui.BaseActivity +import com.github.kr328.clash.ui.theme.MihomoTheme -class OverrideSettingsActivity : DesignActivity() { - override suspend fun main() { - val configuration = withClash { queryOverride(Clash.OverrideSlot.Persist) } +class OverrideSettingsActivity : BaseActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) - defer { withClash { patchOverride(Clash.OverrideSlot.Persist, configuration) } } - - val design = OverrideSettingsDesign(this, configuration) - - setContentDesign(design) - - while (isActive) { - select { - events.onReceive {} - - design.requests.onReceive { - when (it) { - OverrideSettingsDesign.Request.ResetOverride -> { - defer { withClash { clearOverride(Clash.OverrideSlot.Persist) } } - finish() - } - } - } - } - } + setContent { MihomoTheme { OverrideSettingsScreen(onResetCompleted = ::finish) } } } } 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 cd91352378..cad70cf6da 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 @@ -51,7 +51,7 @@ fun MetaFeatureSettingsScreen( onResetCompleted: () -> Unit, ) { val context = LocalContext.current - val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val configuration by viewModel.configuration.collectAsStateWithLifecycle() val importResult by viewModel.importResult.collectAsStateWithLifecycle() val importedText = stringResource(R.string.geofile_imported) var pendingImportType by remember { mutableStateOf(null) } @@ -87,7 +87,7 @@ fun MetaFeatureSettingsScreen( } MetaFeatureSettingsContent( - configuration = uiState, + configuration = configuration, actions = viewModel, modifier = modifier, showResetConfirmDialog = showResetConfirmDialog, @@ -273,7 +273,7 @@ private fun LazyListScope.metaSnifferPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.sniff_http_ports, placeholder = R.string.dont_modify, - value = configuration.sniffer.sniff.http.ports, + values = configuration.sniffer.sniff.http.ports, onValueChange = actions::updateSniffHttpPorts, enabled = enabled, ) @@ -296,7 +296,7 @@ private fun LazyListScope.metaSnifferPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.sniff_tls_ports, placeholder = R.string.dont_modify, - value = configuration.sniffer.sniff.tls.ports, + values = configuration.sniffer.sniff.tls.ports, onValueChange = actions::updateSniffTlsPorts, enabled = enabled, ) @@ -319,7 +319,7 @@ private fun LazyListScope.metaSnifferPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.sniff_quic_ports, placeholder = R.string.dont_modify, - value = configuration.sniffer.sniff.quic.ports, + values = configuration.sniffer.sniff.quic.ports, onValueChange = actions::updateSniffQuicPorts, enabled = enabled, ) @@ -381,7 +381,7 @@ private fun LazyListScope.metaSnifferPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.force_domain, placeholder = R.string.dont_modify, - value = configuration.sniffer.forceDomain, + values = configuration.sniffer.forceDomain, onValueChange = actions::updateForceDomain, enabled = enabled, ) @@ -391,7 +391,7 @@ private fun LazyListScope.metaSnifferPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.skip_domain, placeholder = R.string.dont_modify, - value = configuration.sniffer.skipDomain, + values = configuration.sniffer.skipDomain, onValueChange = actions::updateSkipDomain, enabled = enabled, ) @@ -401,7 +401,7 @@ private fun LazyListScope.metaSnifferPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.skip_src_address, placeholder = R.string.dont_modify, - value = configuration.sniffer.skipSrcAddress, + values = configuration.sniffer.skipSrcAddress, onValueChange = actions::updateSkipSrcAddress, enabled = enabled, ) @@ -411,7 +411,7 @@ private fun LazyListScope.metaSnifferPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.skip_dst_address, placeholder = R.string.dont_modify, - value = configuration.sniffer.skipDstAddress, + values = configuration.sniffer.skipDstAddress, onValueChange = actions::updateSkipDstAddress, enabled = enabled, ) diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsDesign.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt similarity index 71% rename from app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsDesign.kt rename to app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt index 26951d7ca7..6b307316e1 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsDesign.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/ui/OverrideSettingsScreen.kt @@ -1,6 +1,5 @@ package com.github.kr328.clash.settings.ui -import android.content.Context import androidx.annotation.StringRes import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -22,8 +21,8 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -39,47 +38,61 @@ import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import com.github.kr328.clash.R import com.github.kr328.clash.core.model.ConfigurationOverride import com.github.kr328.clash.core.model.LogMessage import com.github.kr328.clash.core.model.TunnelState -import com.github.kr328.clash.ui.Design +import com.github.kr328.clash.settings.vm.OverrideSettingsViewModel import com.github.kr328.clash.ui.component.EmptyEditorContent import com.github.kr328.clash.ui.component.FullScreenPreferenceDialog import com.github.kr328.clash.ui.component.MihomoScaffold import com.github.kr328.clash.ui.component.SettingsEditTextListPreferenceItem import com.github.kr328.clash.ui.component.SettingsListPreferenceItem import com.github.kr328.clash.ui.component.initialTextFieldValue -import com.github.kr328.clash.ui.component.rememberWriteThroughState 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.preferenceCategory -class OverrideSettingsDesign(context: Context, private val configuration: ConfigurationOverride) : - Design(context) { - sealed interface Request { - data object ResetOverride : Request - } +@Composable +@OptIn(ExperimentalMaterial3Api::class) +fun OverrideSettingsScreen( + modifier: Modifier = Modifier, + viewModel: OverrideSettingsViewModel = viewModel(), + onResetCompleted: () -> Unit, +) { + val configuration by viewModel.configuration.collectAsStateWithLifecycle() + var showResetConfirmDialog by remember { mutableStateOf(false) } - @Composable - override fun Content() = MihomoTheme { - OverrideSettingsScreen( - configuration = configuration, - onResetConfirmed = { requests.trySend(Request.ResetOverride) }, - ) - } + DisposableEffect(viewModel) { onDispose { viewModel.persistOverride() } } + + OverrideSettingsContent( + configuration = configuration, + actions = viewModel, + modifier = modifier, + showResetConfirmDialog = showResetConfirmDialog, + onShowResetConfirmDialogChange = { showResetConfirmDialog = it }, + onResetConfirmed = { + viewModel.resetOverride() + onResetCompleted() + }, + ) } @Composable @OptIn(ExperimentalMaterial3Api::class) -private fun OverrideSettingsScreen( +private fun OverrideSettingsContent( configuration: ConfigurationOverride, - onResetConfirmed: () -> Unit, + actions: OverrideSettingsActions, modifier: Modifier = Modifier, + showResetConfirmDialog: Boolean, + onShowResetConfirmDialogChange: (Boolean) -> Unit, + onResetConfirmed: () -> Unit, ) { - var showResetConfirmDialog by remember { mutableStateOf(false) } + val dnsEnabled = configuration.dns.enable val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() MihomoScaffold( @@ -87,7 +100,7 @@ private fun OverrideSettingsScreen( modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), scrollBehavior = scrollBehavior, actions = { - IconButton(onClick = { showResetConfirmDialog = true }) { + IconButton(onClick = { onShowResetConfirmDialogChange(true) }) { Icon( painter = painterResource(R.drawable.ic_baseline_replay), contentDescription = stringResource(R.string.reset), @@ -96,24 +109,20 @@ private fun OverrideSettingsScreen( }, ) { innerPadding -> ProvidePreferenceLocals { - val dnsEnableState = - rememberWriteThroughState(configuration.dns.enable) { configuration.dns.enable = it } - val dnsEnabled by dnsEnableState - LazyColumn(modifier = Modifier.fillMaxSize(), contentPadding = innerPadding) { - generalPreferenceItems(configuration) - dnsPreferenceItems(configuration, dnsEnableState, dnsEnabled) + generalPreferenceItems(configuration, actions) + dnsPreferenceItems(configuration, actions, dnsEnabled) } if (showResetConfirmDialog) { AlertDialog( - onDismissRequest = { showResetConfirmDialog = false }, + onDismissRequest = { onShowResetConfirmDialogChange(false) }, title = { Text(stringResource(R.string.reset_override_settings)) }, text = { Text(stringResource(R.string.reset_override_settings_message)) }, confirmButton = { TextButton( onClick = { - showResetConfirmDialog = false + onShowResetConfirmDialogChange(false) onResetConfirmed() } ) { @@ -121,7 +130,7 @@ private fun OverrideSettingsScreen( } }, dismissButton = { - TextButton(onClick = { showResetConfirmDialog = false }) { + TextButton(onClick = { onShowResetConfirmDialogChange(false) }) { Text(stringResource(R.string.cancel)) } }, @@ -131,17 +140,18 @@ private fun OverrideSettingsScreen( } } -private fun LazyListScope.generalPreferenceItems(configuration: ConfigurationOverride) { +private fun LazyListScope.generalPreferenceItems( + configuration: ConfigurationOverride, + actions: OverrideSettingsActions, +) { 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, - state = - rememberWriteThroughState(portText(configuration.httpPort)) { - configuration.httpPort = parsePort(it) - }, + value = portText(configuration.httpPort), + onValueChange = { actions.updateHttpPort(parsePort(it)) }, numericOnly = true, ) } @@ -150,10 +160,8 @@ private fun LazyListScope.generalPreferenceItems(configuration: ConfigurationOve title = R.string.socks_port, placeholder = R.string.dont_modify, emptyLabel = R.string.disabled, - state = - rememberWriteThroughState(portText(configuration.socksPort)) { - configuration.socksPort = parsePort(it) - }, + value = portText(configuration.socksPort), + onValueChange = { actions.updateSocksPort(parsePort(it)) }, numericOnly = true, ) } @@ -162,10 +170,8 @@ private fun LazyListScope.generalPreferenceItems(configuration: ConfigurationOve title = R.string.redirect_port, placeholder = R.string.dont_modify, emptyLabel = R.string.disabled, - state = - rememberWriteThroughState(portText(configuration.redirectPort)) { - configuration.redirectPort = parsePort(it) - }, + value = portText(configuration.redirectPort), + onValueChange = { actions.updateRedirectPort(parsePort(it)) }, numericOnly = true, ) } @@ -174,10 +180,8 @@ private fun LazyListScope.generalPreferenceItems(configuration: ConfigurationOve title = R.string.tproxy_port, placeholder = R.string.dont_modify, emptyLabel = R.string.disabled, - state = - rememberWriteThroughState(portText(configuration.tproxyPort)) { - configuration.tproxyPort = parsePort(it) - }, + value = portText(configuration.tproxyPort), + onValueChange = { actions.updateTproxyPort(parsePort(it)) }, numericOnly = true, ) } @@ -186,10 +190,8 @@ private fun LazyListScope.generalPreferenceItems(configuration: ConfigurationOve title = R.string.mixed_port, placeholder = R.string.dont_modify, emptyLabel = R.string.disabled, - state = - rememberWriteThroughState(portText(configuration.mixedPort)) { - configuration.mixedPort = parsePort(it) - }, + value = portText(configuration.mixedPort), + onValueChange = { actions.updateMixedPort(parsePort(it)) }, numericOnly = true, ) } @@ -197,33 +199,29 @@ private fun LazyListScope.generalPreferenceItems(configuration: ConfigurationOve SettingsEditTextListPreferenceItem( title = R.string.authentication, placeholder = R.string.dont_modify, - state = - rememberWriteThroughState(configuration.authentication) { - configuration.authentication = it - }, + values = configuration.authentication, + onValueChange = actions::updateAuthentication, ) } item(key = "allowLan", contentType = "ListPreference") { - val state = rememberWriteThroughState(configuration.allowLan) { configuration.allowLan = it } - val value by state SettingsListPreferenceItem( - state = state, + value = configuration.allowLan, + onValueChange = actions::updateAllowLan, values = booleanOptions, modifier = Modifier.fillMaxWidth(), title = R.string.allow_lan, - summary = value.textRes, + summary = configuration.allowLan.textRes, valueToText = { it.textRes }, ) } item(key = "ipv6", contentType = "ListPreference") { - val state = rememberWriteThroughState(configuration.ipv6) { configuration.ipv6 = it } - val value by state SettingsListPreferenceItem( - state = state, + value = configuration.ipv6, + onValueChange = actions::updateIpv6, values = booleanOptions, modifier = Modifier.fillMaxWidth(), title = R.string.ipv6, - summary = value.textRes, + summary = configuration.ipv6.textRes, valueToText = { it.textRes }, ) } @@ -232,8 +230,8 @@ private fun LazyListScope.generalPreferenceItems(configuration: ConfigurationOve title = R.string.bind_address, placeholder = R.string.dont_modify, emptyLabel = R.string.default_, - state = - rememberWriteThroughState(configuration.bindAddress) { configuration.bindAddress = it }, + value = configuration.bindAddress, + onValueChange = actions::updateBindAddress, ) } item(key = "externalController", contentType = "EditTextPreference") { @@ -241,10 +239,8 @@ private fun LazyListScope.generalPreferenceItems(configuration: ConfigurationOve title = R.string.external_controller, placeholder = R.string.dont_modify, emptyLabel = R.string.default_, - state = - rememberWriteThroughState(configuration.externalController) { - configuration.externalController = it - }, + value = configuration.externalController, + onValueChange = actions::updateExternalController, ) } item(key = "externalControllerTls", contentType = "EditTextPreference") { @@ -252,34 +248,26 @@ private fun LazyListScope.generalPreferenceItems(configuration: ConfigurationOve title = R.string.external_controller_tls, placeholder = R.string.dont_modify, emptyLabel = R.string.default_, - state = - rememberWriteThroughState(configuration.externalControllerTLS) { - configuration.externalControllerTLS = it - }, + value = configuration.externalControllerTLS, + onValueChange = actions::updateExternalControllerTls, ) } item(key = "allowOrigins", contentType = "EditTextListPreference") { SettingsEditTextListPreferenceItem( title = R.string.allow_origins, placeholder = R.string.dont_modify, - state = - rememberWriteThroughState(configuration.externalControllerCors.allowOrigins) { - configuration.externalControllerCors.allowOrigins = it - }, + values = configuration.externalControllerCors.allowOrigins, + onValueChange = actions::updateAllowOrigins, ) } item(key = "allowPrivateNetwork", contentType = "ListPreference") { - val state = - rememberWriteThroughState(configuration.externalControllerCors.allowPrivateNetwork) { - configuration.externalControllerCors.allowPrivateNetwork = it - } - val value by state SettingsListPreferenceItem( - state = state, + value = configuration.externalControllerCors.allowPrivateNetwork, + onValueChange = actions::updateAllowPrivateNetwork, values = booleanOptions, modifier = Modifier.fillMaxWidth(), title = R.string.allow_private_network, - summary = value.textRes, + summary = configuration.externalControllerCors.allowPrivateNetwork.textRes, valueToText = { it.textRes }, ) } @@ -288,30 +276,29 @@ private fun LazyListScope.generalPreferenceItems(configuration: ConfigurationOve title = R.string.secret, placeholder = R.string.dont_modify, emptyLabel = R.string.default_, - state = rememberWriteThroughState(configuration.secret) { configuration.secret = it }, + value = configuration.secret, + onValueChange = actions::updateSecret, ) } item(key = "mode", contentType = "ListPreference") { - val state = rememberWriteThroughState(configuration.mode) { configuration.mode = it } - val value by state SettingsListPreferenceItem( - state = state, + value = configuration.mode, + onValueChange = actions::updateMode, values = TunnelState.Mode.entries, modifier = Modifier.fillMaxWidth(), title = R.string.mode, - summary = value.textRes, + summary = configuration.mode.textRes, valueToText = { it.textRes }, ) } item(key = "logLevel", contentType = "ListPreference") { - val state = rememberWriteThroughState(configuration.logLevel) { configuration.logLevel = it } - val value by state SettingsListPreferenceItem( - state = state, + value = configuration.logLevel, + onValueChange = actions::updateLogLevel, values = LogMessage.Level.entries, modifier = Modifier.fillMaxWidth(), title = R.string.log_level, - summary = value.textRes, + summary = configuration.logLevel.textRes, valueToText = { it.textRes }, ) } @@ -319,20 +306,22 @@ private fun LazyListScope.generalPreferenceItems(configuration: ConfigurationOve OverrideEditTextMapPreferenceItem( title = R.string.hosts, placeholder = R.string.dont_modify, - state = rememberWriteThroughState(configuration.hosts) { configuration.hosts = it }, + values = configuration.hosts, + onValueChange = actions::updateHosts, ) } } private fun LazyListScope.dnsPreferenceItems( configuration: ConfigurationOverride, - dnsEnableState: MutableState, + actions: OverrideSettingsActions, dnsEnabled: Boolean?, ) { preferenceCategory(key = "cat_dns", title = { Text(stringResource(R.string.dns)) }) item(key = "dnsStrategy", contentType = "ListPreference") { SettingsListPreferenceItem( - state = dnsEnableState, + value = dnsEnabled, + onValueChange = actions::updateDnsEnable, values = booleanOptions, modifier = Modifier.fillMaxWidth(), title = R.string.strategy, @@ -341,16 +330,14 @@ private fun LazyListScope.dnsPreferenceItems( ) } item(key = "dnsPreferH3", contentType = "ListPreference") { - val state = - rememberWriteThroughState(configuration.dns.preferH3) { configuration.dns.preferH3 = it } - val value by state SettingsListPreferenceItem( - state = state, + value = configuration.dns.preferH3, + onValueChange = actions::updateDnsPreferH3, values = booleanOptions, modifier = Modifier.fillMaxWidth(), enabled = dnsEnabled != false, title = R.string.prefer_h3, - summary = value.textRes, + summary = configuration.dns.preferH3.textRes, valueToText = { it.textRes }, ) } @@ -359,66 +346,56 @@ private fun LazyListScope.dnsPreferenceItems( title = R.string.listen, placeholder = R.string.dont_modify, emptyLabel = R.string.disabled, - state = rememberWriteThroughState(configuration.dns.listen) { configuration.dns.listen = it }, + value = configuration.dns.listen, + onValueChange = actions::updateDnsListen, enabled = dnsEnabled != false, ) } item(key = "appendSystemDns", contentType = "ListPreference") { - val state = - rememberWriteThroughState(configuration.app.appendSystemDns) { - configuration.app.appendSystemDns = it - } - val value by state SettingsListPreferenceItem( - state = state, + value = configuration.app.appendSystemDns, + onValueChange = actions::updateAppendSystemDns, values = booleanOptions, modifier = Modifier.fillMaxWidth(), enabled = dnsEnabled != false, title = R.string.append_system_dns, - summary = value.textRes, + summary = configuration.app.appendSystemDns.textRes, valueToText = { it.textRes }, ) } item(key = "dnsIpv6", contentType = "ListPreference") { - val state = rememberWriteThroughState(configuration.dns.ipv6) { configuration.dns.ipv6 = it } - val value by state SettingsListPreferenceItem( - state = state, + value = configuration.dns.ipv6, + onValueChange = actions::updateDnsIpv6, values = booleanOptions, modifier = Modifier.fillMaxWidth(), enabled = dnsEnabled != false, title = R.string.ipv6, - summary = value.textRes, + summary = configuration.dns.ipv6.textRes, valueToText = { it.textRes }, ) } item(key = "dnsUseHosts", contentType = "ListPreference") { - val state = - rememberWriteThroughState(configuration.dns.useHosts) { configuration.dns.useHosts = it } - val value by state SettingsListPreferenceItem( - state = state, + value = configuration.dns.useHosts, + onValueChange = actions::updateDnsUseHosts, values = booleanOptions, modifier = Modifier.fillMaxWidth(), enabled = dnsEnabled != false, title = R.string.use_hosts, - summary = value.textRes, + summary = configuration.dns.useHosts.textRes, valueToText = { it.textRes }, ) } item(key = "dnsEnhancedMode", contentType = "ListPreference") { - val state = - rememberWriteThroughState(configuration.dns.enhancedMode) { - configuration.dns.enhancedMode = it - } - val value by state SettingsListPreferenceItem( - state = state, + value = configuration.dns.enhancedMode, + onValueChange = actions::updateDnsEnhancedMode, values = ConfigurationOverride.DnsEnhancedMode.entries, modifier = Modifier.fillMaxWidth(), enabled = dnsEnabled != false, title = R.string.enhanced_mode, - summary = value.textRes, + summary = configuration.dns.enhancedMode.textRes, valueToText = { it.textRes }, ) } @@ -426,10 +403,8 @@ private fun LazyListScope.dnsPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.name_server, placeholder = R.string.dont_modify, - state = - rememberWriteThroughState(configuration.dns.nameServer) { - configuration.dns.nameServer = it - }, + values = configuration.dns.nameServer, + onValueChange = actions::updateDnsNameServer, enabled = dnsEnabled != false, ) } @@ -437,8 +412,8 @@ private fun LazyListScope.dnsPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.fallback, placeholder = R.string.dont_modify, - state = - rememberWriteThroughState(configuration.dns.fallback) { configuration.dns.fallback = it }, + values = configuration.dns.fallback, + onValueChange = actions::updateDnsFallback, enabled = dnsEnabled != false, ) } @@ -446,10 +421,8 @@ private fun LazyListScope.dnsPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.default_name_server, placeholder = R.string.dont_modify, - state = - rememberWriteThroughState(configuration.dns.defaultServer) { - configuration.dns.defaultServer = it - }, + values = configuration.dns.defaultServer, + onValueChange = actions::updateDnsDefaultServer, enabled = dnsEnabled != false, ) } @@ -457,42 +430,32 @@ private fun LazyListScope.dnsPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.fakeip_filter, placeholder = R.string.dont_modify, - state = - rememberWriteThroughState(configuration.dns.fakeIpFilter) { - configuration.dns.fakeIpFilter = it - }, + values = configuration.dns.fakeIpFilter, + onValueChange = actions::updateDnsFakeIpFilter, enabled = dnsEnabled != false, ) } item(key = "dnsFakeIpFilterMode", contentType = "ListPreference") { - val state = - rememberWriteThroughState(configuration.dns.fakeIPFilterMode) { - configuration.dns.fakeIPFilterMode = it - } - val value by state SettingsListPreferenceItem( - state = state, + value = configuration.dns.fakeIPFilterMode, + onValueChange = actions::updateDnsFakeIpFilterMode, values = ConfigurationOverride.FilterMode.entries, modifier = Modifier.fillMaxWidth(), enabled = dnsEnabled != false, title = R.string.fakeip_filter_mode, - summary = value.textRes, + summary = configuration.dns.fakeIPFilterMode.textRes, valueToText = { it.textRes }, ) } item(key = "dnsGeoIpFallback", contentType = "ListPreference") { - val state = - rememberWriteThroughState(configuration.dns.fallbackFilter.geoIp) { - configuration.dns.fallbackFilter.geoIp = it - } - val value by state SettingsListPreferenceItem( - state = state, + value = configuration.dns.fallbackFilter.geoIp, + onValueChange = actions::updateDnsGeoIpFallback, values = booleanOptions, modifier = Modifier.fillMaxWidth(), enabled = dnsEnabled != false, title = R.string.geoip_fallback, - summary = value.textRes, + summary = configuration.dns.fallbackFilter.geoIp.textRes, valueToText = { it.textRes }, ) } @@ -501,10 +464,8 @@ private fun LazyListScope.dnsPreferenceItems( title = R.string.geoip_fallback_code, placeholder = R.string.dont_modify, emptyLabel = R.string.raw_cn, - state = - rememberWriteThroughState(configuration.dns.fallbackFilter.geoIpCode) { - configuration.dns.fallbackFilter.geoIpCode = it - }, + value = configuration.dns.fallbackFilter.geoIpCode, + onValueChange = actions::updateDnsGeoIpCode, enabled = dnsEnabled != false, ) } @@ -512,10 +473,8 @@ private fun LazyListScope.dnsPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.domain_fallback, placeholder = R.string.dont_modify, - state = - rememberWriteThroughState(configuration.dns.fallbackFilter.domain) { - configuration.dns.fallbackFilter.domain = it - }, + values = configuration.dns.fallbackFilter.domain, + onValueChange = actions::updateDnsDomainFallback, enabled = dnsEnabled != false, ) } @@ -523,10 +482,8 @@ private fun LazyListScope.dnsPreferenceItems( SettingsEditTextListPreferenceItem( title = R.string.ipcidr_fallback, placeholder = R.string.dont_modify, - state = - rememberWriteThroughState(configuration.dns.fallbackFilter.ipcidr) { - configuration.dns.fallbackFilter.ipcidr = it - }, + values = configuration.dns.fallbackFilter.ipcidr, + onValueChange = actions::updateDnsIpcidrFallback, enabled = dnsEnabled != false, ) } @@ -534,10 +491,8 @@ private fun LazyListScope.dnsPreferenceItems( OverrideEditTextMapPreferenceItem( title = R.string.name_server_policy, placeholder = R.string.dont_modify, - state = - rememberWriteThroughState(configuration.dns.nameserverPolicy) { - configuration.dns.nameserverPolicy = it - }, + values = configuration.dns.nameserverPolicy, + onValueChange = actions::updateDnsNameserverPolicy, enabled = dnsEnabled != false, ) } @@ -548,17 +503,17 @@ private fun OverrideEditTextPreferenceItem( @StringRes title: Int, @StringRes placeholder: Int, @StringRes emptyLabel: Int, - state: MutableState, + value: String?, + onValueChange: (String?) -> Unit, enabled: Boolean = true, numericOnly: Boolean = false, ) { - var text by state var showDialog by remember { mutableStateOf(false) } val summary = when { - text == null -> stringResource(placeholder) - text.isNullOrEmpty() -> stringResource(emptyLabel) - else -> text.orEmpty() + value == null -> stringResource(placeholder) + value.isEmpty() -> stringResource(emptyLabel) + else -> value } Preference( modifier = Modifier.fillMaxWidth(), @@ -568,11 +523,12 @@ private fun OverrideEditTextPreferenceItem( onClick = { showDialog = true }, ) if (showDialog) { - var inputText by remember { - mutableStateOf( - TextFieldValue(text = text.orEmpty(), selection = TextRange(text.orEmpty().length)) - ) - } + 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) { @@ -599,12 +555,13 @@ private fun OverrideEditTextPreferenceItem( confirmButton = { TextButton( onClick = { - text = + onValueChange( if (numericOnly) { portText(parsePort(inputText.text)) } else { inputText.text } + ) showDialog = false } ) { @@ -615,7 +572,7 @@ private fun OverrideEditTextPreferenceItem( Row { TextButton( onClick = { - text = null + onValueChange(null) showDialog = false } ) { @@ -632,10 +589,10 @@ private fun OverrideEditTextPreferenceItem( private fun OverrideEditTextMapPreferenceItem( @StringRes title: Int, @StringRes placeholder: Int, - state: MutableState?>, + values: Map?, + onValueChange: (Map?) -> Unit, enabled: Boolean = true, ) { - var values by state var showDialog by remember { mutableStateOf(false) } Preference( modifier = Modifier.fillMaxWidth(), @@ -650,7 +607,7 @@ private fun OverrideEditTextMapPreferenceItem( initialValues = values, onDismiss = { showDialog = false }, onApply = { - values = it + onValueChange(it) showDialog = false }, ) @@ -704,8 +661,8 @@ private fun EditableTextMapDialog( MapEntryInputDialog( title = title, onDismiss = { showAddDialog = false }, - onConfirm = { key, value -> - values = values + (key to value) + onConfirm = { key, valueText -> + values = values + (key to valueText) showAddDialog = false }, ) @@ -855,8 +812,84 @@ private fun parsePort(text: String?): Int? = else -> text.toIntOrNull() ?: 0 } +interface OverrideSettingsActions { + fun updateHttpPort(value: Int?) = Unit + + fun updateSocksPort(value: Int?) = Unit + + fun updateRedirectPort(value: Int?) = Unit + + fun updateTproxyPort(value: Int?) = Unit + + fun updateMixedPort(value: Int?) = Unit + + fun updateAuthentication(value: List?) = Unit + + fun updateAllowLan(value: Boolean?) = Unit + + fun updateIpv6(value: Boolean?) = Unit + + fun updateBindAddress(value: String?) = Unit + + fun updateExternalController(value: String?) = Unit + + fun updateExternalControllerTls(value: String?) = Unit + + fun updateAllowOrigins(value: List?) = Unit + + fun updateAllowPrivateNetwork(value: Boolean?) = Unit + + fun updateSecret(value: String?) = Unit + + fun updateMode(value: TunnelState.Mode?) = Unit + + fun updateLogLevel(value: LogMessage.Level?) = Unit + + fun updateHosts(value: Map?) = Unit + + fun updateDnsEnable(value: Boolean?) = Unit + + fun updateDnsPreferH3(value: Boolean?) = Unit + + fun updateDnsListen(value: String?) = Unit + + fun updateAppendSystemDns(value: Boolean?) = Unit + + fun updateDnsIpv6(value: Boolean?) = Unit + + fun updateDnsUseHosts(value: Boolean?) = Unit + + fun updateDnsEnhancedMode(value: ConfigurationOverride.DnsEnhancedMode?) = Unit + + fun updateDnsNameServer(value: List?) = Unit + + fun updateDnsFallback(value: List?) = Unit + + fun updateDnsDefaultServer(value: List?) = Unit + + fun updateDnsFakeIpFilter(value: List?) = Unit + + fun updateDnsFakeIpFilterMode(value: ConfigurationOverride.FilterMode?) = Unit + + fun updateDnsGeoIpFallback(value: Boolean?) = Unit + + fun updateDnsGeoIpCode(value: String?) = Unit + + fun updateDnsDomainFallback(value: List?) = Unit + + fun updateDnsIpcidrFallback(value: List?) = Unit + + fun updateDnsNameserverPolicy(value: Map?) = Unit +} + @PreviewMihomo @Composable -private fun OverrideSettingsScreenPreview() = MihomoTheme { - OverrideSettingsScreen(configuration = ConfigurationOverride(), onResetConfirmed = {}) +private fun OverrideSettingsContentPreview() = MihomoTheme { + OverrideSettingsContent( + configuration = ConfigurationOverride(), + actions = object : OverrideSettingsActions {}, + showResetConfirmDialog = false, + onShowResetConfirmDialogChange = {}, + onResetConfirmed = {}, + ) } diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/vm/MetaFeatureSettingsViewModel.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/vm/MetaFeatureSettingsViewModel.kt index 6b81c4b1a6..0e80779b28 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/settings/vm/MetaFeatureSettingsViewModel.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/vm/MetaFeatureSettingsViewModel.kt @@ -7,7 +7,7 @@ import android.provider.OpenableColumns import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.github.kr328.clash.core.Clash -import com.github.kr328.clash.core.model.ConfigurationOverride as UiState +import com.github.kr328.clash.core.model.ConfigurationOverride import com.github.kr328.clash.settings.ui.MetaFeatureSettingsActions import com.github.kr328.clash.util.clashDir import com.github.kr328.clash.util.withClash @@ -25,15 +25,15 @@ class MetaFeatureSettingsViewModel(app: Application) : private val validDatabaseExtensions = listOf(".metadb", ".db", ".dat", ".mmdb") @Volatile private var skipPersist = false - val uiState: StateFlow - field = MutableStateFlow(UiState()) + val configuration: StateFlow + field = MutableStateFlow(ConfigurationOverride()) val importResult: StateFlow field = MutableStateFlow(ImportResult.NotStart) init { viewModelScope.launch { - uiState.value = withClash { queryOverride(Clash.OverrideSlot.Persist) } + configuration.value = withClash { queryOverride(Clash.OverrideSlot.Persist) } } } @@ -41,7 +41,7 @@ class MetaFeatureSettingsViewModel(app: Application) : // Intended to use non-viewModel scope as we need the action to be called on disposed. CoroutineScope(Dispatchers.IO).launch { if (skipPersist) return@launch - withClash { patchOverride(Clash.OverrideSlot.Persist, uiState.value) } + withClash { patchOverride(Clash.OverrideSlot.Persist, configuration.value) } } } @@ -96,25 +96,28 @@ class MetaFeatureSettingsViewModel(app: Application) : } } - override fun updateUnifiedDelay(value: Boolean?) = uiState.update { + override fun updateUnifiedDelay(value: Boolean?) = configuration.update { it.copy(unifiedDelay = value) } - override fun updateGeodataMode(value: Boolean?) = uiState.update { it.copy(geodataMode = value) } + override fun updateGeodataMode(value: Boolean?) = configuration.update { + it.copy(geodataMode = value) + } - override fun updateTcpConcurrent(value: Boolean?) = uiState.update { + override fun updateTcpConcurrent(value: Boolean?) = configuration.update { it.copy(tcpConcurrent = value) } - override fun updateFindProcessMode(value: UiState.FindProcessMode?) = uiState.update { - it.copy(findProcessMode = value) - } + override fun updateFindProcessMode(value: ConfigurationOverride.FindProcessMode?) = + configuration.update { + it.copy(findProcessMode = value) + } - override fun updateSnifferEnable(value: Boolean?) = uiState.update { + override fun updateSnifferEnable(value: Boolean?) = configuration.update { it.copy(sniffer = it.sniffer.copy(enable = value)) } - override fun updateSniffHttpPorts(value: List?) = uiState.update { + override fun updateSniffHttpPorts(value: List?) = configuration.update { it.copy( sniffer = it.sniffer.copy( @@ -123,7 +126,7 @@ class MetaFeatureSettingsViewModel(app: Application) : ) } - override fun updateSniffHttpOverrideDestination(value: Boolean?) = uiState.update { + override fun updateSniffHttpOverrideDestination(value: Boolean?) = configuration.update { it.copy( sniffer = it.sniffer.copy( @@ -133,7 +136,7 @@ class MetaFeatureSettingsViewModel(app: Application) : ) } - override fun updateSniffTlsPorts(value: List?) = uiState.update { + override fun updateSniffTlsPorts(value: List?) = configuration.update { it.copy( sniffer = it.sniffer.copy( @@ -142,7 +145,7 @@ class MetaFeatureSettingsViewModel(app: Application) : ) } - override fun updateSniffTlsOverrideDestination(value: Boolean?) = uiState.update { + override fun updateSniffTlsOverrideDestination(value: Boolean?) = configuration.update { it.copy( sniffer = it.sniffer.copy( @@ -152,7 +155,7 @@ class MetaFeatureSettingsViewModel(app: Application) : ) } - override fun updateSniffQuicPorts(value: List?) = uiState.update { + override fun updateSniffQuicPorts(value: List?) = configuration.update { it.copy( sniffer = it.sniffer.copy( @@ -161,7 +164,7 @@ class MetaFeatureSettingsViewModel(app: Application) : ) } - override fun updateSniffQuicOverrideDestination(value: Boolean?) = uiState.update { + override fun updateSniffQuicOverrideDestination(value: Boolean?) = configuration.update { it.copy( sniffer = it.sniffer.copy( @@ -171,31 +174,31 @@ class MetaFeatureSettingsViewModel(app: Application) : ) } - override fun updateForceDnsMapping(value: Boolean?) = uiState.update { + override fun updateForceDnsMapping(value: Boolean?) = configuration.update { it.copy(sniffer = it.sniffer.copy(forceDnsMapping = value)) } - override fun updateParsePureIp(value: Boolean?) = uiState.update { + override fun updateParsePureIp(value: Boolean?) = configuration.update { it.copy(sniffer = it.sniffer.copy(parsePureIp = value)) } - override fun updateOverrideDestination(value: Boolean?) = uiState.update { + override fun updateOverrideDestination(value: Boolean?) = configuration.update { it.copy(sniffer = it.sniffer.copy(overrideDestination = value)) } - override fun updateForceDomain(value: List?) = uiState.update { + override fun updateForceDomain(value: List?) = configuration.update { it.copy(sniffer = it.sniffer.copy(forceDomain = value)) } - override fun updateSkipDomain(value: List?) = uiState.update { + override fun updateSkipDomain(value: List?) = configuration.update { it.copy(sniffer = it.sniffer.copy(skipDomain = value)) } - override fun updateSkipSrcAddress(value: List?) = uiState.update { + override fun updateSkipSrcAddress(value: List?) = configuration.update { it.copy(sniffer = it.sniffer.copy(skipSrcAddress = value)) } - override fun updateSkipDstAddress(value: List?) = uiState.update { + override fun updateSkipDstAddress(value: List?) = configuration.update { it.copy(sniffer = it.sniffer.copy(skipDstAddress = value)) } diff --git a/app/src/main/kotlin/com/github/kr328/clash/settings/vm/OverrideSettingsViewModel.kt b/app/src/main/kotlin/com/github/kr328/clash/settings/vm/OverrideSettingsViewModel.kt new file mode 100644 index 0000000000..ecdd60d7ea --- /dev/null +++ b/app/src/main/kotlin/com/github/kr328/clash/settings/vm/OverrideSettingsViewModel.kt @@ -0,0 +1,168 @@ +package com.github.kr328.clash.settings.vm + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.github.kr328.clash.core.Clash +import com.github.kr328.clash.core.model.ConfigurationOverride +import com.github.kr328.clash.core.model.LogMessage +import com.github.kr328.clash.core.model.TunnelState +import com.github.kr328.clash.settings.ui.OverrideSettingsActions +import com.github.kr328.clash.util.withClash +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class OverrideSettingsViewModel(app: Application) : AndroidViewModel(app), OverrideSettingsActions { + @Volatile private var skipPersist = false + + val configuration: StateFlow + field = MutableStateFlow(ConfigurationOverride()) + + init { + viewModelScope.launch { + configuration.value = withClash { queryOverride(Clash.OverrideSlot.Persist) } + } + } + + fun persistOverride() { + // Intended to use non-viewModel scope as we need the action to be called on disposed. + CoroutineScope(Dispatchers.IO).launch { + if (skipPersist) return@launch + withClash { patchOverride(Clash.OverrideSlot.Persist, configuration.value) } + } + } + + fun resetOverride() { + skipPersist = true + // Intended to use non-viewModel scope as the action might be called on disposed. + CoroutineScope(Dispatchers.IO).launch { + withClash { clearOverride(Clash.OverrideSlot.Persist) } + } + } + + override fun updateHttpPort(value: Int?) = configuration.update { it.copy(httpPort = value) } + + override fun updateSocksPort(value: Int?) = configuration.update { it.copy(socksPort = value) } + + override fun updateRedirectPort(value: Int?) = configuration.update { + it.copy(redirectPort = value) + } + + override fun updateTproxyPort(value: Int?) = configuration.update { it.copy(tproxyPort = value) } + + override fun updateMixedPort(value: Int?) = configuration.update { it.copy(mixedPort = value) } + + override fun updateAuthentication(value: List?) = configuration.update { + it.copy(authentication = value) + } + + override fun updateAllowLan(value: Boolean?) = configuration.update { it.copy(allowLan = value) } + + override fun updateIpv6(value: Boolean?) = configuration.update { it.copy(ipv6 = value) } + + override fun updateBindAddress(value: String?) = configuration.update { + it.copy(bindAddress = value) + } + + override fun updateExternalController(value: String?) = configuration.update { + it.copy(externalController = value) + } + + override fun updateExternalControllerTls(value: String?) = configuration.update { + it.copy(externalControllerTLS = value) + } + + override fun updateAllowOrigins(value: List?) = configuration.update { + it.copy(externalControllerCors = it.externalControllerCors.copy(allowOrigins = value)) + } + + override fun updateAllowPrivateNetwork(value: Boolean?) = configuration.update { + it.copy(externalControllerCors = it.externalControllerCors.copy(allowPrivateNetwork = value)) + } + + override fun updateSecret(value: String?) = configuration.update { it.copy(secret = value) } + + override fun updateMode(value: TunnelState.Mode?) = configuration.update { it.copy(mode = value) } + + override fun updateLogLevel(value: LogMessage.Level?) = configuration.update { + it.copy(logLevel = value) + } + + override fun updateHosts(value: Map?) = configuration.update { + it.copy(hosts = value) + } + + override fun updateDnsEnable(value: Boolean?) = configuration.update { + it.copy(dns = it.dns.copy(enable = value)) + } + + override fun updateDnsPreferH3(value: Boolean?) = configuration.update { + it.copy(dns = it.dns.copy(preferH3 = value)) + } + + override fun updateDnsListen(value: String?) = configuration.update { + it.copy(dns = it.dns.copy(listen = value)) + } + + override fun updateAppendSystemDns(value: Boolean?) = configuration.update { + it.copy(app = it.app.copy(appendSystemDns = value)) + } + + override fun updateDnsIpv6(value: Boolean?) = configuration.update { + it.copy(dns = it.dns.copy(ipv6 = value)) + } + + override fun updateDnsUseHosts(value: Boolean?) = configuration.update { + it.copy(dns = it.dns.copy(useHosts = value)) + } + + override fun updateDnsEnhancedMode(value: ConfigurationOverride.DnsEnhancedMode?) = + configuration.update { + it.copy(dns = it.dns.copy(enhancedMode = value)) + } + + override fun updateDnsNameServer(value: List?) = configuration.update { + it.copy(dns = it.dns.copy(nameServer = value)) + } + + override fun updateDnsFallback(value: List?) = configuration.update { + it.copy(dns = it.dns.copy(fallback = value)) + } + + override fun updateDnsDefaultServer(value: List?) = configuration.update { + it.copy(dns = it.dns.copy(defaultServer = value)) + } + + override fun updateDnsFakeIpFilter(value: List?) = configuration.update { + it.copy(dns = it.dns.copy(fakeIpFilter = value)) + } + + override fun updateDnsFakeIpFilterMode(value: ConfigurationOverride.FilterMode?) = + configuration.update { + it.copy(dns = it.dns.copy(fakeIPFilterMode = value)) + } + + override fun updateDnsGeoIpFallback(value: Boolean?) = configuration.update { + it.copy(dns = it.dns.copy(fallbackFilter = it.dns.fallbackFilter.copy(geoIp = value))) + } + + override fun updateDnsGeoIpCode(value: String?) = configuration.update { + it.copy(dns = it.dns.copy(fallbackFilter = it.dns.fallbackFilter.copy(geoIpCode = value))) + } + + override fun updateDnsDomainFallback(value: List?) = configuration.update { + it.copy(dns = it.dns.copy(fallbackFilter = it.dns.fallbackFilter.copy(domain = value))) + } + + override fun updateDnsIpcidrFallback(value: List?) = configuration.update { + it.copy(dns = it.dns.copy(fallbackFilter = it.dns.fallbackFilter.copy(ipcidr = value))) + } + + override fun updateDnsNameserverPolicy(value: Map?) = configuration.update { + it.copy(dns = it.dns.copy(nameserverPolicy = value)) + } +} diff --git a/app/src/main/kotlin/com/github/kr328/clash/ui/component/SettingsPreference.kt b/app/src/main/kotlin/com/github/kr328/clash/ui/component/SettingsPreference.kt index 8af6362c86..2b00bd290f 100644 --- a/app/src/main/kotlin/com/github/kr328/clash/ui/component/SettingsPreference.kt +++ b/app/src/main/kotlin/com/github/kr328/clash/ui/component/SettingsPreference.kt @@ -21,7 +21,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -43,24 +42,6 @@ import com.github.kr328.clash.ui.theme.mihomoDimens import me.zhanghai.compose.preference.ListPreference import me.zhanghai.compose.preference.Preference -@Composable -fun rememberWriteThroughState(initial: T, sync: (T) -> Unit): MutableState = remember { - object : MutableState { - private val inner = mutableStateOf(initial) - - override var value: T - get() = inner.value - set(v) { - inner.value = v - sync(v) - } - - override fun component1(): T = value - - override fun component2(): (T) -> Unit = { value = it } - } -} - @Composable fun SettingsListPreferenceItem( value: T, @@ -84,64 +65,14 @@ fun SettingsListPreferenceItem( ) } -@Composable -fun SettingsListPreferenceItem( - state: MutableState, - values: List, - @StringRes title: Int, - @StringRes summary: Int, - modifier: Modifier = Modifier, - enabled: Boolean = true, - valueToText: @Composable (T) -> Int, -) { - ListPreference( - state = state, - values = values, - modifier = modifier, - enabled = enabled, - title = { Text(stringResource(title)) }, - summary = { Text(stringResource(summary)) }, - valueToText = { AnnotatedString(stringResource(valueToText(it))) }, - ) -} - @Composable fun SettingsEditTextListPreferenceItem( @StringRes title: Int, @StringRes placeholder: Int, - value: List?, + values: List?, onValueChange: (List?) -> Unit, enabled: Boolean = true, ) { - var showDialog by remember { mutableStateOf(false) } - Preference( - modifier = Modifier.fillMaxWidth(), - title = { Text(stringResource(title)) }, - summary = { Text(value.summary(placeholder)) }, - enabled = enabled, - onClick = { showDialog = true }, - ) - if (showDialog) { - EditableTextListDialog( - title = title, - initialValues = value, - onDismiss = { showDialog = false }, - onApply = { - onValueChange(it) - showDialog = false - }, - ) - } -} - -@Composable -fun SettingsEditTextListPreferenceItem( - @StringRes title: Int, - @StringRes placeholder: Int, - state: MutableState?>, - enabled: Boolean = true, -) { - var values by state var showDialog by remember { mutableStateOf(false) } Preference( modifier = Modifier.fillMaxWidth(), @@ -156,7 +87,7 @@ fun SettingsEditTextListPreferenceItem( initialValues = values, onDismiss = { showDialog = false }, onApply = { - values = it + onValueChange(it) showDialog = false }, )