From 240131a023e6bd1addd0b226eb616e59a703d765 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 6 Aug 2024 23:08:47 +0900 Subject: [PATCH 1/4] android, desktop: move some network settings to advanced network settings (#4583) * android, desktop: move some network settings to advanced network settings * strings * icon * string * fix * change * icon and footer * paddings * revert debug lines --- .../chat/simplex/common/model/SimpleXAPI.kt | 12 +- .../common/views/helpers/CloseSheetBar.kt | 2 +- .../common/views/migration/MigrateToDevice.kt | 2 - .../common/views/remote/ConnectMobileView.kt | 12 +- .../usersettings/AdvancedNetworkSettings.kt | 324 +++++++---- .../common/views/usersettings/CallSettings.kt | 5 +- .../views/usersettings/NetworkAndServers.kt | 502 +++++------------- .../common/views/usersettings/SettingsView.kt | 2 +- .../commonMain/resources/MR/ar/strings.xml | 5 - .../commonMain/resources/MR/base/strings.xml | 15 +- .../commonMain/resources/MR/bg/strings.xml | 5 - .../commonMain/resources/MR/cs/strings.xml | 5 - .../commonMain/resources/MR/de/strings.xml | 5 - .../commonMain/resources/MR/es/strings.xml | 5 - .../commonMain/resources/MR/fa/strings.xml | 5 - .../commonMain/resources/MR/fi/strings.xml | 5 - .../commonMain/resources/MR/fr/strings.xml | 5 - .../commonMain/resources/MR/hu/strings.xml | 5 - .../commonMain/resources/MR/it/strings.xml | 5 - .../commonMain/resources/MR/iw/strings.xml | 5 - .../commonMain/resources/MR/ja/strings.xml | 5 - .../commonMain/resources/MR/ko/strings.xml | 4 - .../commonMain/resources/MR/lt/strings.xml | 5 - .../commonMain/resources/MR/ml/strings.xml | 1 - .../commonMain/resources/MR/nl/strings.xml | 5 - .../commonMain/resources/MR/pl/strings.xml | 5 - .../resources/MR/pt-rBR/strings.xml | 5 - .../commonMain/resources/MR/pt/strings.xml | 4 - .../commonMain/resources/MR/ro/strings.xml | 1 - .../commonMain/resources/MR/ru/strings.xml | 5 - .../commonMain/resources/MR/th/strings.xml | 5 - .../commonMain/resources/MR/tr/strings.xml | 5 - .../commonMain/resources/MR/uk/strings.xml | 5 - .../resources/MR/zh-rCN/strings.xml | 5 - .../resources/MR/zh-rTW/strings.xml | 5 - 35 files changed, 378 insertions(+), 618 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 51ec214554..4e503244e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -142,8 +142,8 @@ class AppPreferences { }, set = fun(mode: TransportSessionMode) { _networkSessionMode.set(mode.name) } ) - val networkSMPProxyMode = mkStrPreference(SHARED_PREFS_NETWORK_SMP_PROXY_MODE, SMPProxyMode.Never.name) - val networkSMPProxyFallback = mkStrPreference(SHARED_PREFS_NETWORK_SMP_PROXY_FALLBACK, SMPProxyFallback.Allow.name) + val networkSMPProxyMode = mkStrPreference(SHARED_PREFS_NETWORK_SMP_PROXY_MODE, NetCfg.defaults.smpProxyMode.name) + val networkSMPProxyFallback = mkStrPreference(SHARED_PREFS_NETWORK_SMP_PROXY_FALLBACK, NetCfg.defaults.smpProxyFallback.name) val networkHostMode = mkStrPreference(SHARED_PREFS_NETWORK_HOST_MODE, HostMode.OnionViaSocks.name) val networkRequiredHostMode = mkBoolPreference(SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE, false) val networkTCPConnectTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT, NetCfg.defaults.tcpConnectTimeout, NetCfg.proxyDefaults.tcpConnectTimeout) @@ -3589,10 +3589,6 @@ enum class SMPProxyMode { @SerialName("unknown") Unknown, @SerialName("unprotected") Unprotected, @SerialName("never") Never; - - companion object { - val default = Never - } } @Serializable @@ -3600,10 +3596,6 @@ enum class SMPProxyFallback { @SerialName("allow") Allow, @SerialName("allowProtected") AllowProtected, @SerialName("prohibit") Prohibit; - - companion object { - val default = Allow - } } @Serializable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt index da1e59fa70..0ff68de3a0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt @@ -70,7 +70,7 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Co } @Composable -fun AppBarTitle(title: String, hostDevice: Pair? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f) { +fun AppBarTitle(title: String, hostDevice: Pair? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp) { val theme = CurrentColors.collectAsState() val titleColor = MaterialTheme.appColors.title val brush = if (theme.value.base == DefaultTheme.SIMPLEX) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index 1a94676f79..bfb5c350a5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -237,7 +237,6 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos proxy } } - val proxyPort = remember { derivedStateOf { networkProxyHostPort.value?.split(":")?.lastOrNull()?.toIntOrNull() ?: 9050 } } val netCfg = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(getNetCfg().withOnionHosts(onionHosts.value).copy(socksProxy = socksProxy, sessionMode = sessionMode.value)) @@ -275,7 +274,6 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos onionHosts, sessionMode, networkProxyHostPortPref, - proxyPort, toggleSocksProxy = { enable -> networkUseSocksProxy.value = enable }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt index d8c9557863..92503f273e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt @@ -89,10 +89,7 @@ fun ConnectMobileLayout( connectDesktop: () -> Unit, deleteHost: (RemoteHostInfo) -> Unit, ) { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { + ColumnWithScrollBar(Modifier.fillMaxWidth()) { AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles)) SectionView(generalGetString(MR.strings.this_device_name).uppercase()) { DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) } @@ -100,7 +97,7 @@ fun ConnectMobileLayout( PreferenceToggle(stringResource(MR.strings.multicast_discoverable_via_local_network), checked = remember { controller.appPrefs.offerRemoteMulticast.state }.value) { controller.appPrefs.offerRemoteMulticast.set(it) } - SectionDividerSpaced(maxBottomPadding = false) + SectionDividerSpaced() } SectionView(stringResource(MR.strings.devices).uppercase()) { if (chatModel.localUserCreated.value == true) { @@ -179,10 +176,7 @@ private fun ConnectMobileViewLayout( refreshQrCode: () -> Unit = {}, UnderQrLayout: @Composable () -> Unit = {}, ) { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { + ColumnWithScrollBar(Modifier.fillMaxWidth()) { if (title != null) { AppBarTitle(title) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt index cd3e5902d0..ed68c7b013 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt @@ -2,39 +2,54 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionCustomFooter +import SectionDividerSpaced import SectionItemView +import SectionItemWithValue import SectionView +import SectionViewSelectableCards import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* -import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.platform.ColumnWithScrollBar +import chat.simplex.common.platform.chatModel import chat.simplex.res.MR import java.text.DecimalFormat @Composable -fun AdvancedNetworkSettingsView(chatModel: ChatModel) { - val currentCfg = remember { mutableStateOf(chatModel.controller.getNetCfg()) } +fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> Unit, close: () -> Unit) { + val currentRemoteHost by remember { chatModel.currentRemoteHost } + val developerTools = remember { appPrefs.developerTools.get() } + + // Will be actual once the screen is re-opened + val savedCfg = remember { mutableStateOf(controller.getNetCfg()) } + // Will have an edited state when the screen is re-opened + val currentCfg = remember { stateGetOrPut("currentCfg") { controller.getNetCfg() } } val currentCfgVal = currentCfg.value // used only on initialization + + val onionHosts = remember { mutableStateOf(currentCfgVal.onionHosts) } + val sessionMode = remember { mutableStateOf(currentCfgVal.sessionMode) } + val smpProxyMode = remember { mutableStateOf(currentCfgVal.smpProxyMode) } + val smpProxyFallback = remember { mutableStateOf(currentCfgVal.smpProxyFallback) } + + val networkUseSocksProxy: MutableState = remember { mutableStateOf(currentCfgVal.useSocksProxy) } val networkTCPConnectTimeout = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout) } val networkTCPTimeout = remember { mutableStateOf(currentCfgVal.tcpTimeout) } val networkTCPTimeoutPerKb = remember { mutableStateOf(currentCfgVal.tcpTimeoutPerKb) } - var networkRcvConcurrency = remember { mutableStateOf(currentCfgVal.rcvConcurrency) } + val networkRcvConcurrency = remember { mutableStateOf(currentCfgVal.rcvConcurrency) } val networkSMPPingInterval = remember { mutableStateOf(currentCfgVal.smpPingInterval) } val networkSMPPingCount = remember { mutableStateOf(currentCfgVal.smpPingCount) } val networkEnableKeepAlive = remember { mutableStateOf(currentCfgVal.enableKeepAlive) } @@ -63,11 +78,11 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) { } return NetCfg( socksProxy = currentCfg.value.socksProxy, - hostMode = currentCfg.value.hostMode, - requiredHostMode = currentCfg.value.requiredHostMode, - sessionMode = currentCfg.value.sessionMode, - smpProxyMode = currentCfg.value.smpProxyMode, - smpProxyFallback = currentCfg.value.smpProxyFallback, +// hostMode = currentCfg.value.hostMode, +// requiredHostMode = currentCfg.value.requiredHostMode, + sessionMode = sessionMode.value, + smpProxyMode = smpProxyMode.value, + smpProxyFallback = smpProxyFallback.value, tcpConnectTimeout = networkTCPConnectTimeout.value, tcpTimeout = networkTCPTimeout.value, tcpTimeoutPerKb = networkTCPTimeoutPerKb.value, @@ -75,10 +90,14 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) { tcpKeepAlive = tcpKeepAlive, smpPingInterval = networkSMPPingInterval.value, smpPingCount = networkSMPPingCount.value - ) + ).withOnionHosts(onionHosts.value) } fun updateView(cfg: NetCfg) { + onionHosts.value = cfg.onionHosts + sessionMode.value = cfg.sessionMode + smpProxyMode.value = cfg.smpProxyMode + smpProxyFallback.value = cfg.smpProxyFallback networkTCPConnectTimeout.value = cfg.tcpConnectTimeout networkTCPTimeout.value = cfg.tcpTimeout networkTCPTimeoutPerKb.value = cfg.tcpTimeoutPerKb @@ -97,40 +116,80 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) { } } - fun saveCfg(cfg: NetCfg) { + fun saveCfg(cfg: NetCfg, close: (() -> Unit)? = null) { withBGApi { - chatModel.controller.apiSetNetworkConfig(cfg) - currentCfg.value = cfg - chatModel.controller.setNetCfg(cfg) + if (chatModel.controller.apiSetNetworkConfig(cfg)) { + currentCfg.value = cfg + savedCfg.value = cfg + chatModel.controller.setNetCfg(cfg) + close?.invoke() + } } } fun reset() { val newCfg = if (currentCfg.value.useSocksProxy) NetCfg.proxyDefaults else NetCfg.defaults updateView(newCfg) - saveCfg(newCfg) + currentCfg.value = newCfg } - AdvancedNetworkSettingsLayout( - networkTCPConnectTimeout, - networkTCPTimeout, - networkTCPTimeoutPerKb, - networkRcvConcurrency, - networkSMPPingInterval, - networkSMPPingCount, - networkEnableKeepAlive, - networkTCPKeepIdle, - networkTCPKeepIntvl, - networkTCPKeepCnt, - resetDisabled = if (currentCfg.value.useSocksProxy) currentCfg.value == NetCfg.proxyDefaults else currentCfg.value == NetCfg.defaults, - reset = { showUpdateNetworkSettingsDialog(::reset) }, - footerDisabled = buildCfg() == currentCfg.value, - revert = { updateView(currentCfg.value) }, - save = { showUpdateNetworkSettingsDialog { saveCfg(buildCfg()) } } - ) + val saveDisabled = buildCfg() == savedCfg.value + + ModalView( + close = { + if (saveDisabled) { + close() + } else { + showUnsavedChangesAlert({ + saveCfg(buildCfg(), close) + }, close) + } + }, + ) { + AdvancedNetworkSettingsLayout( + currentRemoteHost = currentRemoteHost, + networkUseSocksProxy = networkUseSocksProxy, + developerTools = developerTools, + onionHosts = onionHosts, + useOnion = { onionHosts.value = it; currentCfg.value = currentCfg.value.withOnionHosts(it) }, + sessionMode = sessionMode, + smpProxyMode = smpProxyMode, + smpProxyFallback = smpProxyFallback, + networkTCPConnectTimeout, + networkTCPTimeout, + networkTCPTimeoutPerKb, + networkRcvConcurrency, + networkSMPPingInterval, + networkSMPPingCount, + networkEnableKeepAlive, + networkTCPKeepIdle, + networkTCPKeepIntvl, + networkTCPKeepCnt, + updateSessionMode = { sessionMode.value = it; currentCfg.value = currentCfg.value.copy(sessionMode = it) }, + updateSMPProxyMode = { smpProxyMode.value = it; currentCfg.value = currentCfg.value.copy(smpProxyMode = it) }, + updateSMPProxyFallback = { smpProxyFallback.value = it; currentCfg.value = currentCfg.value.copy(smpProxyFallback = it) }, + showModal = showModal, + resetDisabled = if (currentCfg.value.useSocksProxy) buildCfg() == NetCfg.proxyDefaults else buildCfg() == NetCfg.defaults, + reset = ::reset, + saveDisabled = saveDisabled, + save = { + showUpdateNetworkSettingsDialog { + saveCfg(buildCfg()) + } + } + ) + } } @Composable fun AdvancedNetworkSettingsLayout( + currentRemoteHost: RemoteHostInfo?, + networkUseSocksProxy: State, + developerTools: Boolean, + onionHosts: MutableState, + useOnion: (OnionHosts) -> Unit, + sessionMode: MutableState, + smpProxyMode: MutableState, + smpProxyFallback: MutableState, networkTCPConnectTimeout: MutableState, networkTCPTimeout: MutableState, networkTCPTimeoutPerKb: MutableState, @@ -141,10 +200,13 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) { networkTCPKeepIdle: MutableState, networkTCPKeepIntvl: MutableState, networkTCPKeepCnt: MutableState, + updateSessionMode: (TransportSessionMode) -> Unit, + updateSMPProxyMode: (SMPProxyMode) -> Unit, + updateSMPProxyFallback: (SMPProxyFallback) -> Unit, + showModal: (ModalData.() -> Unit) -> Unit, resetDisabled: Boolean, reset: () -> Unit, - footerDisabled: Boolean, - revert: () -> Unit, + saveDisabled: Boolean, save: () -> Unit ) { val secondsLabel = stringResource(MR.strings.network_option_seconds_label) @@ -154,10 +216,39 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) { .fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.network_settings_title)) - SectionView { - SectionItemView { - ResetToDefaultsButton(reset, disabled = resetDisabled) + + if (currentRemoteHost == null) { + SectionView(generalGetString(MR.strings.settings_section_title_private_message_routing)) { + SMPProxyModePicker(smpProxyMode, showModal, updateSMPProxyMode) + SMPProxyFallbackPicker(smpProxyFallback, showModal, updateSMPProxyFallback, enabled = remember { derivedStateOf { smpProxyMode.value != SMPProxyMode.Never } }) + SettingsPreferenceItem(painterResource(MR.images.ic_arrow_forward), stringResource(MR.strings.private_routing_show_message_status), chatModel.controller.appPrefs.showSentViaProxy) } + SectionCustomFooter { + Text(stringResource(MR.strings.private_routing_explanation)) + } + SectionDividerSpaced(maxTopPadding = true) + } + + if (currentRemoteHost == null && networkUseSocksProxy.value) { + SectionView(stringResource(MR.strings.network_socks_proxy).uppercase()) { + UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) + SectionCustomFooter { + Column { + Text(annotatedStringResource(MR.strings.disable_onion_hosts_when_not_supported)) + } + } + } + SectionDividerSpaced(maxTopPadding = true) + } + + if (currentRemoteHost == null && developerTools) { + SectionView(stringResource(MR.strings.network_session_mode_transport_isolation).uppercase()) { + SessionModePicker(sessionMode, showModal, updateSessionMode) + } + SectionDividerSpaced() + } + + SectionView(stringResource(MR.strings.network_option_tcp_connection).uppercase()) { SectionItemView { TimeoutSettingRow( stringResource(MR.strings.network_option_tcp_connection_timeout), networkTCPConnectTimeout, @@ -220,23 +311,92 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) { } } } - SectionCustomFooter { - SettingsSectionFooter(revert, save, footerDisabled) + + SectionDividerSpaced(maxBottomPadding = false) + + SectionView { + SectionItemView(reset, disabled = resetDisabled) { + Text(stringResource(MR.strings.network_options_reset_to_defaults), color = if (resetDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) + } + SectionItemView(save, disabled = saveDisabled) { + Text(stringResource(MR.strings.network_options_save_and_reconnect), color = if (saveDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) + } } SectionBottomSpacer() } } @Composable -fun ResetToDefaultsButton(reset: () -> Unit, disabled: Boolean) { - val modifier = if (disabled) Modifier else Modifier.clickable { reset() } - Row( - modifier.fillMaxSize(), - verticalAlignment = Alignment.CenterVertically - ) { - val color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary - Text(stringResource(MR.strings.network_options_reset_to_defaults), color = color) +private fun SMPProxyModePicker( + smpProxyMode: MutableState, + showModal: (@Composable ModalData.() -> Unit) -> Unit, + updateSMPProxyMode: (SMPProxyMode) -> Unit, +) { + val density = LocalDensity.current + val values = remember { + SMPProxyMode.values().map { + when (it) { + SMPProxyMode.Always -> ValueTitleDesc(SMPProxyMode.Always, generalGetString(MR.strings.network_smp_proxy_mode_always), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_mode_always_description), density)) + SMPProxyMode.Unknown -> ValueTitleDesc(SMPProxyMode.Unknown, generalGetString(MR.strings.network_smp_proxy_mode_unknown), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_mode_unknown_description), density)) + SMPProxyMode.Unprotected -> ValueTitleDesc(SMPProxyMode.Unprotected, generalGetString(MR.strings.network_smp_proxy_mode_unprotected), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_mode_unprotected_description), density)) + SMPProxyMode.Never -> ValueTitleDesc(SMPProxyMode.Never, generalGetString(MR.strings.network_smp_proxy_mode_never), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_mode_never_description), density)) + } + } } + + SectionItemWithValue( + generalGetString(MR.strings.network_smp_proxy_mode_private_routing), + smpProxyMode, + values, + icon = painterResource(MR.images.ic_settings_ethernet), + onSelected = { + showModal { + ColumnWithScrollBar( + Modifier.fillMaxWidth(), + ) { + AppBarTitle(stringResource(MR.strings.network_smp_proxy_mode_private_routing)) + SectionViewSelectableCards(null, smpProxyMode, values, updateSMPProxyMode) + } + } + } + ) +} + +@Composable +private fun SMPProxyFallbackPicker( + smpProxyFallback: MutableState, + showModal: (@Composable ModalData.() -> Unit) -> Unit, + updateSMPProxyFallback: (SMPProxyFallback) -> Unit, + enabled: State, +) { + val density = LocalDensity.current + val values = remember { + SMPProxyFallback.values().map { + when (it) { + SMPProxyFallback.Allow -> ValueTitleDesc(SMPProxyFallback.Allow, generalGetString(MR.strings.network_smp_proxy_fallback_allow), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_fallback_allow_description), density)) + SMPProxyFallback.AllowProtected -> ValueTitleDesc(SMPProxyFallback.AllowProtected, generalGetString(MR.strings.network_smp_proxy_fallback_allow_protected), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_fallback_allow_protected_description), density)) + SMPProxyFallback.Prohibit -> ValueTitleDesc(SMPProxyFallback.Prohibit, generalGetString(MR.strings.network_smp_proxy_fallback_prohibit), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_fallback_prohibit_description), density)) + } + } + } + + SectionItemWithValue( + generalGetString(MR.strings.network_smp_proxy_fallback_allow_downgrade), + smpProxyFallback, + values, + icon = painterResource(MR.images.ic_arrows_left_right), + enabled = enabled, + onSelected = { + showModal { + ColumnWithScrollBar( + Modifier.fillMaxWidth(), + ) { + AppBarTitle(stringResource(MR.strings.network_smp_proxy_fallback_allow_downgrade)) + SectionViewSelectableCards(null, smpProxyFallback, values, updateSMPProxyFallback) + } + } + } + ) } @Composable @@ -377,43 +537,6 @@ fun TimeoutSettingRow(title: String, selection: MutableState, values: List } } -@Composable -fun SettingsSectionFooter(revert: () -> Unit, save: () -> Unit, disabled: Boolean) { - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - FooterButton(painterResource(MR.images.ic_replay), stringResource(MR.strings.network_options_revert), revert, disabled) - FooterButton(painterResource(MR.images.ic_check), stringResource(MR.strings.network_options_save), save, disabled) - } -} - -@Composable -fun FooterButton(icon: Painter, title: String, action: () -> Unit, disabled: Boolean) { - Surface( - shape = RoundedCornerShape(20.dp), - color = Color.Black.copy(alpha = 0f), - contentColor = LocalContentColor.current - ) { - val modifier = if (disabled) Modifier else Modifier.clickable { action() } - Row( - modifier.padding(8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Icon( - icon, - title, - tint = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary - ) - Text( - title, - color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary - ) - } - } -} - fun showUpdateNetworkSettingsDialog(action: () -> Unit) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.update_network_settings_question), @@ -423,11 +546,27 @@ fun showUpdateNetworkSettingsDialog(action: () -> Unit) { ) } +private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { + AlertManager.shared.showAlertDialogStacked( + title = generalGetString(MR.strings.update_network_settings_question), + confirmText = generalGetString(MR.strings.network_options_save_and_reconnect), + dismissText = generalGetString(MR.strings.exit_without_saving), + onConfirm = save, + onDismiss = revert, + ) +} + @Preview @Composable fun PreviewAdvancedNetworkSettingsLayout() { SimpleXTheme { AdvancedNetworkSettingsLayout( + currentRemoteHost = null, + networkUseSocksProxy = remember { mutableStateOf(false) }, + developerTools = false, + sessionMode = remember { mutableStateOf(TransportSessionMode.User) }, + smpProxyMode = remember { mutableStateOf(SMPProxyMode.Never) }, + smpProxyFallback = remember { mutableStateOf(SMPProxyFallback.Allow) }, networkTCPConnectTimeout = remember { mutableStateOf(10_000000) }, networkTCPTimeout = remember { mutableStateOf(10_000000) }, networkTCPTimeoutPerKb = remember { mutableStateOf(10_000) }, @@ -438,10 +577,15 @@ fun PreviewAdvancedNetworkSettingsLayout() { networkTCPKeepIdle = remember { mutableStateOf(10) }, networkTCPKeepIntvl = remember { mutableStateOf(10) }, networkTCPKeepCnt = remember { mutableStateOf(10) }, + onionHosts = remember { mutableStateOf(OnionHosts.PREFER) }, + useOnion = {}, + updateSessionMode = {}, + updateSMPProxyMode = {}, + updateSMPProxyFallback = {}, + showModal = {}, resetDisabled = false, reset = {}, - footerDisabled = false, - revert = {}, + saveDisabled = false, save = {} ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt index 94415b0ee0..468a192f09 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt @@ -36,10 +36,7 @@ fun CallSettingsLayout( callOnLockScreen: SharedPreference, editIceServers: () -> Unit, ) { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { + ColumnWithScrollBar(Modifier.fillMaxWidth()) { AppBarTitle(stringResource(MR.strings.your_calls)) val lockCallState = remember { mutableStateOf(callOnLockScreen.get()) } SectionView(stringResource(MR.strings.settings_section_title_settings)) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index 61c8e1b75f..62ba287879 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -7,9 +7,7 @@ import SectionItemView import SectionItemWithValue import SectionView import SectionViewSelectable -import SectionViewSelectableCards import TextIconSpaced -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardActions import androidx.compose.material.* @@ -20,20 +18,17 @@ import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.* -import androidx.compose.ui.text.font.* import androidx.compose.ui.text.input.* import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.chat.item.ClickableText import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.helpers.annotatedStringResource import chat.simplex.res.MR @Composable @@ -42,22 +37,11 @@ fun NetworkAndServersView() { // It's not a state, just a one-time value. Shouldn't be used in any state-related situations val netCfg = remember { chatModel.controller.getNetCfg() } val networkUseSocksProxy: MutableState = remember { mutableStateOf(netCfg.useSocksProxy) } - val developerTools = chatModel.controller.appPrefs.developerTools.get() - val onionHosts = remember { mutableStateOf(netCfg.onionHosts) } - val sessionMode = remember { mutableStateOf(netCfg.sessionMode) } - val smpProxyMode = remember { mutableStateOf(netCfg.smpProxyMode) } - val smpProxyFallback = remember { mutableStateOf(netCfg.smpProxyFallback) } val proxyPort = remember { derivedStateOf { chatModel.controller.appPrefs.networkProxyHostPort.state.value?.split(":")?.lastOrNull()?.toIntOrNull() ?: 9050 } } NetworkAndServersLayout( currentRemoteHost = currentRemoteHost, - developerTools = developerTools, networkUseSocksProxy = networkUseSocksProxy, - onionHosts = onionHosts, - sessionMode = sessionMode, - smpProxyMode = smpProxyMode, - smpProxyFallback = smpProxyFallback, - proxyPort = proxyPort, toggleSocksProxy = { enable -> val def = NetCfg.defaults val proxyDef = NetCfg.proxyDefaults @@ -84,7 +68,6 @@ fun NetworkAndServersView() { chatModel.controller.apiSetNetworkConfig(conf) chatModel.controller.setNetCfg(conf) networkUseSocksProxy.value = true - onionHosts.value = conf.onionHosts } } ) @@ -111,184 +94,48 @@ fun NetworkAndServersView() { chatModel.controller.apiSetNetworkConfig(conf) chatModel.controller.setNetCfg(conf) networkUseSocksProxy.value = false - onionHosts.value = conf.onionHosts } } ) } - }, - useOnion = { - if (onionHosts.value == it) return@NetworkAndServersLayout - val prevValue = onionHosts.value - onionHosts.value = it - val startsWith = when (it) { - OnionHosts.NEVER -> generalGetString(MR.strings.network_use_onion_hosts_no_desc_in_alert) - OnionHosts.PREFER -> generalGetString(MR.strings.network_use_onion_hosts_prefer_desc_in_alert) - OnionHosts.REQUIRED -> generalGetString(MR.strings.network_use_onion_hosts_required_desc_in_alert) - } - showUpdateNetworkSettingsDialog( - title = generalGetString(MR.strings.update_onion_hosts_settings_question), - startsWith, - onDismiss = { - onionHosts.value = prevValue - } - ) { - withBGApi { - val newCfg = chatModel.controller.getNetCfg().withOnionHosts(it) - val res = chatModel.controller.apiSetNetworkConfig(newCfg) - if (res) { - chatModel.controller.setNetCfg(newCfg) - onionHosts.value = it - } else { - onionHosts.value = prevValue - } - } - } - }, - updateSessionMode = { - if (sessionMode.value == it) return@NetworkAndServersLayout - val prevValue = sessionMode.value - sessionMode.value = it - val startsWith = when (it) { - TransportSessionMode.User -> generalGetString(MR.strings.network_session_mode_user_description) - TransportSessionMode.Entity -> generalGetString(MR.strings.network_session_mode_entity_description) - } - showUpdateNetworkSettingsDialog( - title = generalGetString(MR.strings.update_network_session_mode_question), - startsWith, - onDismiss = { sessionMode.value = prevValue } - ) { - withBGApi { - val newCfg = chatModel.controller.getNetCfg().copy(sessionMode = it) - val res = chatModel.controller.apiSetNetworkConfig(newCfg) - if (res) { - chatModel.controller.setNetCfg(newCfg) - sessionMode.value = it - } else { - sessionMode.value = prevValue - } - } - } - }, - updateSMPProxyMode = { - if (smpProxyMode.value == it) return@NetworkAndServersLayout - val prevValue = smpProxyMode.value - smpProxyMode.value = it - val startsWith = when (it) { - SMPProxyMode.Always -> generalGetString(MR.strings.network_smp_proxy_mode_always_description) - SMPProxyMode.Unknown -> generalGetString(MR.strings.network_smp_proxy_mode_unknown_description) - SMPProxyMode.Unprotected -> generalGetString(MR.strings.network_smp_proxy_mode_unprotected_description) - SMPProxyMode.Never -> generalGetString(MR.strings.network_smp_proxy_mode_never_description) - } - showUpdateNetworkSettingsDialog( - title = generalGetString(MR.strings.update_network_smp_proxy_mode_question), - startsWith, - onDismiss = { smpProxyMode.value = prevValue } - ) { - withBGApi { - val newCfg = chatModel.controller.getNetCfg().copy(smpProxyMode = it) - val res = chatModel.controller.apiSetNetworkConfig(newCfg) - if (res) { - chatModel.controller.setNetCfg(newCfg) - smpProxyMode.value = it - } else { - smpProxyMode.value = prevValue - } - } - } - }, - updateSMPProxyFallback = { - if (smpProxyFallback.value == it) return@NetworkAndServersLayout - val prevValue = smpProxyFallback.value - smpProxyFallback.value = it - val startsWith = when (it) { - SMPProxyFallback.Allow -> generalGetString(MR.strings.network_smp_proxy_fallback_allow_description) - SMPProxyFallback.AllowProtected -> generalGetString(MR.strings.network_smp_proxy_fallback_allow_protected_description) - SMPProxyFallback.Prohibit -> generalGetString(MR.strings.network_smp_proxy_fallback_prohibit_description) - } - showUpdateNetworkSettingsDialog( - title = generalGetString(MR.strings.update_network_smp_proxy_fallback_question), - startsWith, - onDismiss = { smpProxyFallback.value = prevValue } - ) { - withBGApi { - val newCfg = chatModel.controller.getNetCfg().copy(smpProxyFallback = it) - val res = chatModel.controller.apiSetNetworkConfig(newCfg) - if (res) { - chatModel.controller.setNetCfg(newCfg) - smpProxyFallback.value = it - } else { - smpProxyFallback.value = prevValue - } - } - } } ) } @Composable fun NetworkAndServersLayout( currentRemoteHost: RemoteHostInfo?, - developerTools: Boolean, networkUseSocksProxy: MutableState, - onionHosts: MutableState, - sessionMode: MutableState, - smpProxyMode: MutableState, - smpProxyFallback: MutableState, - proxyPort: State, toggleSocksProxy: (Boolean) -> Unit, - useOnion: (OnionHosts) -> Unit, - updateSessionMode: (TransportSessionMode) -> Unit, - updateSMPProxyMode: (SMPProxyMode) -> Unit, - updateSMPProxyFallback: (SMPProxyFallback) -> Unit, ) { val m = chatModel - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { + ColumnWithScrollBar(Modifier.fillMaxWidth()) { val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.start.showModal(content = it) } + val showCustomModal = { it: @Composable (close: () -> Unit) -> Unit -> ModalManager.start.showCustomModal { close -> it(close) }} AppBarTitle(stringResource(MR.strings.network_and_servers)) if (!chatModel.desktopNoUserNoRemote) { SectionView(generalGetString(MR.strings.settings_section_title_messages)) { - SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.smp_servers), { ModalManager.start.showCustomModal { close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.SMP, close) } }) + SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.message_servers), { ModalManager.start.showCustomModal { close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.SMP, close) } }) - SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.xftp_servers), { ModalManager.start.showCustomModal { close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.XFTP, close) } }) + SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.media_and_file_servers), { ModalManager.start.showCustomModal { close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.XFTP, close) } }) if (currentRemoteHost == null) { - UseSocksProxySwitch(networkUseSocksProxy, proxyPort, toggleSocksProxy, showModal, chatModel.controller.appPrefs.networkProxyHostPort, false) - UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) - if (developerTools) { - SessionModePicker(sessionMode, showModal, updateSessionMode) + UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy) + SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, appPrefs.networkProxyHostPort, false, it) }}) + SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), { ModalManager.start.showCustomModal { AdvancedNetworkSettingsView(showModal, it) } }) + if (networkUseSocksProxy.value) { + SectionCustomFooter { + Column { + Text(annotatedStringResource(MR.strings.socks_proxy_setting_limitations)) + } + } + SectionDividerSpaced(maxTopPadding = true) + } else { + SectionDividerSpaced() } - SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), { ModalManager.start.showModal { AdvancedNetworkSettingsView(m) } }) } } } - if (currentRemoteHost == null && networkUseSocksProxy.value) { - SectionCustomFooter { - Column { - Text(annotatedStringResource(MR.strings.disable_onion_hosts_when_not_supported)) - Spacer(Modifier.height(DEFAULT_PADDING_HALF)) - Text(annotatedStringResource(MR.strings.socks_proxy_setting_limitations)) - } - } - Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 32.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp)) - } else if (!chatModel.desktopNoUserNoRemote) { - Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 24.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp)) - } - - if (currentRemoteHost == null) { - SectionView(generalGetString(MR.strings.settings_section_title_private_message_routing)) { - SMPProxyModePicker(smpProxyMode, showModal, updateSMPProxyMode) - SMPProxyFallbackPicker(smpProxyFallback, showModal, updateSMPProxyFallback, enabled = remember { mutableStateOf(smpProxyMode.value != SMPProxyMode.Never) }) - SettingsPreferenceItem(painterResource(MR.images.ic_arrow_forward), stringResource(MR.strings.private_routing_show_message_status), chatModel.controller.appPrefs.showSentViaProxy) - } - SectionCustomFooter { - Text(stringResource(MR.strings.private_routing_explanation)) - } - Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 32.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp)) - } SectionView(generalGetString(MR.strings.settings_section_title_calls)) { SettingsActionItem(painterResource(MR.images.ic_electrical_services), stringResource(MR.strings.webrtc_ice_servers), { ModalManager.start.showModal { RTCServersView(m) } }) @@ -313,13 +160,14 @@ fun NetworkAndServersView() { onionHosts: MutableState, sessionMode: MutableState, networkProxyHostPort: SharedPreference, - proxyPort: State, toggleSocksProxy: (Boolean) -> Unit, useOnion: (OnionHosts) -> Unit, updateSessionMode: (TransportSessionMode) -> Unit, ) { val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.fullscreen.showModal(content = it) } - UseSocksProxySwitch(networkUseSocksProxy, proxyPort, toggleSocksProxy, showModal, networkProxyHostPort, true) + val showCustomModal = { it: @Composable (close: () -> Unit) -> Unit -> ModalManager.fullscreen.showCustomModal { close -> it(close) }} + UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy) + SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, networkProxyHostPort, true, it) } }) UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) if (developerTools) { SessionModePicker(sessionMode, showModal, updateSessionMode) @@ -329,11 +177,7 @@ fun NetworkAndServersView() { @Composable fun UseSocksProxySwitch( networkUseSocksProxy: MutableState, - proxyPort: State, toggleSocksProxy: (Boolean) -> Unit, - showModal: (@Composable ModalData.() -> Unit) -> Unit, - networkProxyHostPort: SharedPreference = chatModel.controller.appPrefs.networkProxyHostPort, - migration: Boolean = false, ) { Row( Modifier.fillMaxWidth().padding(end = DEFAULT_PADDING), @@ -350,32 +194,7 @@ fun UseSocksProxySwitch( tint = MaterialTheme.colors.secondary ) TextIconSpaced(false) - val text = buildAnnotatedString { - append(generalGetString(MR.strings.network_socks_toggle_use_socks_proxy) + " (") - val style = SpanStyle(color = MaterialTheme.colors.primary) - val disabledStyle = SpanStyle(color = MaterialTheme.colors.onBackground) - withAnnotation(tag = "PORT", annotation = generalGetString(MR.strings.network_proxy_port).format(proxyPort.value)) { - withStyle(if (networkUseSocksProxy.value || !migration) style else disabledStyle) { - append(generalGetString(MR.strings.network_proxy_port).format(proxyPort.value)) - } - } - append(")") - } - ClickableText( - text, - style = TextStyle(color = MaterialTheme.colors.onBackground, fontSize = 16.sp, fontFamily = Inter, fontWeight = FontWeight.Normal), - onClick = { offset -> - text.getStringAnnotations(tag = "PORT", start = offset, end = offset) - .firstOrNull()?.let { _ -> - if (networkUseSocksProxy.value || !migration) { - showModal { SockProxySettings(chatModel, networkProxyHostPort, migration) } - } - } - }, - shouldConsumeEvent = { offset -> - text.getStringAnnotations(tag = "PORT", start = offset, end = offset).any() - } - ) + Text(generalGetString(MR.strings.network_socks_toggle_use_socks_proxy)) } DefaultSwitch( checked = networkUseSocksProxy.value, @@ -385,94 +204,124 @@ fun UseSocksProxySwitch( } @Composable -fun SockProxySettings( - m: ChatModel, - networkProxyHostPort: SharedPreference = m.controller.appPrefs.networkProxyHostPort, +fun SocksProxySettings( + networkUseSocksProxy: Boolean, + networkProxyHostPort: SharedPreference = appPrefs.networkProxyHostPort, migration: Boolean, + close: () -> Unit ) { - ColumnWithScrollBar( - Modifier - .fillMaxWidth() - ) { - val defaultHostPort = remember { "localhost:9050" } - AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings)) - val hostPortSaved by remember { networkProxyHostPort.state } - val hostUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.firstOrNull() ?: "localhost")) - } - val portUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.lastOrNull() ?: "9050")) - } - val save = { + val defaultHostPort = remember { "localhost:9050" } + val hostPortSaved by remember { networkProxyHostPort.state } + val hostUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.firstOrNull() ?: "localhost")) + } + val portUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.lastOrNull() ?: "9050")) + } + val save = { + val oldValue = networkProxyHostPort.get() + networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text) + if (networkUseSocksProxy && !migration) { withBGApi { - networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text) - if (m.controller.appPrefs.networkUseSocksProxy.get() && !migration) { - m.controller.apiSetNetworkConfig(m.controller.getNetCfg()) + if (!controller.apiSetNetworkConfig(controller.getNetCfg())) { + networkProxyHostPort.set(oldValue) } } } - SectionView { - SectionItemView { - ResetToDefaultsButton({ - val reset = { - networkProxyHostPort.set(defaultHostPort) - val newHost = defaultHostPort.split(":").first() - val newPort = defaultHostPort.split(":").last() - hostUnsaved.value = hostUnsaved.value.copy(newHost, TextRange(newHost.length)) - portUnsaved.value = portUnsaved.value.copy(newPort, TextRange(newPort.length)) - save() - } - if (m.controller.appPrefs.networkUseSocksProxy.get() && !migration) { - showUpdateNetworkSettingsDialog { - reset() - } - } else { - reset() - } - }, disabled = hostPortSaved == defaultHostPort) - } - SectionItemView { - DefaultConfigurableTextField( - hostUnsaved, - stringResource(MR.strings.host_verb), - modifier = Modifier.fillMaxWidth(), - isValid = ::validHost, - keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), - keyboardType = KeyboardType.Text, - ) - } - SectionItemView { - DefaultConfigurableTextField( - portUnsaved, - stringResource(MR.strings.port_verb), - modifier = Modifier.fillMaxWidth(), - isValid = ::validPort, - keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save() }), - keyboardType = KeyboardType.Number, - ) + } + val saveAndClose = { + val oldValue = networkProxyHostPort.get() + networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text) + if (networkUseSocksProxy && !migration) { + withBGApi { + if (controller.apiSetNetworkConfig(controller.getNetCfg())) { + close() + } else { + networkProxyHostPort.set(oldValue) + } } } - SectionCustomFooter { - NetworkSectionFooter( - revert = { - val prevHost = hostPortSaved?.split(":")?.firstOrNull() ?: "localhost" - val prevPort = hostPortSaved?.split(":")?.lastOrNull() ?: "9050" - hostUnsaved.value = hostUnsaved.value.copy(prevHost, TextRange(prevHost.length)) - portUnsaved.value = portUnsaved.value.copy(prevPort, TextRange(prevPort.length)) - }, - save = { if (m.controller.appPrefs.networkUseSocksProxy.get() && !migration) showUpdateNetworkSettingsDialog { save() } else save() }, - revertDisabled = hostPortSaved == (hostUnsaved.value.text + ":" + portUnsaved.value.text), - saveDisabled = hostPortSaved == (hostUnsaved.value.text + ":" + portUnsaved.value.text) || - remember { derivedStateOf { !validHost(hostUnsaved.value.text) } }.value || - remember { derivedStateOf { !validPort(portUnsaved.value.text) } }.value - ) + } + val saveDisabled = hostPortSaved == (hostUnsaved.value.text + ":" + portUnsaved.value.text) || + remember { derivedStateOf { !validHost(hostUnsaved.value.text) } }.value || + remember { derivedStateOf { !validPort(portUnsaved.value.text) } }.value + val resetDisabled = hostUnsaved.value.text + ":" + portUnsaved.value.text == defaultHostPort + ModalView( + close = { + if (saveDisabled) { + close() + } else { + showUnsavedSocksHostPortAlert( + confirmText = generalGetString(if (networkUseSocksProxy && !migration) MR.strings.network_options_save_and_reconnect else MR.strings.network_options_save), + save = saveAndClose, + close = close + ) + } + }, + ) { + ColumnWithScrollBar( + Modifier + .fillMaxWidth() + ) { + AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings)) + SectionView { + SectionItemView { + DefaultConfigurableTextField( + hostUnsaved, + stringResource(MR.strings.host_verb), + modifier = Modifier.fillMaxWidth(), + isValid = ::validHost, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + keyboardType = KeyboardType.Text, + ) + } + SectionItemView { + DefaultConfigurableTextField( + portUnsaved, + stringResource(MR.strings.port_verb), + modifier = Modifier.fillMaxWidth(), + isValid = ::validPort, + keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save() }), + keyboardType = KeyboardType.Number, + ) + } + } + + Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 27.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp)) + + SectionView { + SectionItemView({ + val newHost = defaultHostPort.split(":").first() + val newPort = defaultHostPort.split(":").last() + hostUnsaved.value = hostUnsaved.value.copy(newHost, TextRange(newHost.length)) + portUnsaved.value = portUnsaved.value.copy(newPort, TextRange(newPort.length)) + }, disabled = resetDisabled) { + Text(stringResource(MR.strings.network_options_reset_to_defaults), color = if (resetDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) + } + SectionItemView( + click = { if (networkUseSocksProxy && !migration) showUpdateNetworkSettingsDialog { save() } else save() }, + disabled = saveDisabled + ) { + Text(stringResource(if (networkUseSocksProxy && !migration) MR.strings.network_options_save_and_reconnect else MR.strings.network_options_save), color = if (saveDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) + } + } + SectionBottomSpacer() } - SectionBottomSpacer() } } +private fun showUnsavedSocksHostPortAlert(confirmText: String, save: () -> Unit, close: () -> Unit) { + AlertManager.shared.showAlertDialogStacked( + title = generalGetString(MR.strings.update_network_settings_question), + confirmText = confirmText, + dismissText = generalGetString(MR.strings.exit_without_saving), + onConfirm = save, + onDismiss = close, + ) +} + @Composable -private fun UseOnionHosts( +fun UseOnionHosts( onionHosts: MutableState, enabled: State, showModal: (@Composable ModalData.() -> Unit) -> Unit, @@ -521,7 +370,7 @@ private fun UseOnionHosts( } @Composable -private fun SessionModePicker( +fun SessionModePicker( sessionMode: MutableState, showModal: (@Composable ModalData.() -> Unit) -> Unit, updateSessionMode: (TransportSessionMode) -> Unit, @@ -554,91 +403,6 @@ private fun SessionModePicker( ) } -@Composable -private fun SMPProxyModePicker( - smpProxyMode: MutableState, - showModal: (@Composable ModalData.() -> Unit) -> Unit, - updateSMPProxyMode: (SMPProxyMode) -> Unit, -) { - val density = LocalDensity.current - val values = remember { - SMPProxyMode.values().map { - when (it) { - SMPProxyMode.Always -> ValueTitleDesc(SMPProxyMode.Always, generalGetString(MR.strings.network_smp_proxy_mode_always), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_mode_always_description), density)) - SMPProxyMode.Unknown -> ValueTitleDesc(SMPProxyMode.Unknown, generalGetString(MR.strings.network_smp_proxy_mode_unknown), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_mode_unknown_description), density)) - SMPProxyMode.Unprotected -> ValueTitleDesc(SMPProxyMode.Unprotected, generalGetString(MR.strings.network_smp_proxy_mode_unprotected), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_mode_unprotected_description), density)) - SMPProxyMode.Never -> ValueTitleDesc(SMPProxyMode.Never, generalGetString(MR.strings.network_smp_proxy_mode_never), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_mode_never_description), density)) - } - } - } - - SectionItemWithValue( - generalGetString(MR.strings.network_smp_proxy_mode_private_routing), - smpProxyMode, - values, - icon = painterResource(MR.images.ic_settings_ethernet), - onSelected = { - showModal { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { - AppBarTitle(stringResource(MR.strings.network_smp_proxy_mode_private_routing)) - SectionViewSelectableCards(null, smpProxyMode, values, updateSMPProxyMode) - } - } - } - ) -} - -@Composable -private fun SMPProxyFallbackPicker( - smpProxyFallback: MutableState, - showModal: (@Composable ModalData.() -> Unit) -> Unit, - updateSMPProxyFallback: (SMPProxyFallback) -> Unit, - enabled: State, -) { - val density = LocalDensity.current - val values = remember { - SMPProxyFallback.values().map { - when (it) { - SMPProxyFallback.Allow -> ValueTitleDesc(SMPProxyFallback.Allow, generalGetString(MR.strings.network_smp_proxy_fallback_allow), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_fallback_allow_description), density)) - SMPProxyFallback.AllowProtected -> ValueTitleDesc(SMPProxyFallback.AllowProtected, generalGetString(MR.strings.network_smp_proxy_fallback_allow_protected), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_fallback_allow_protected_description), density)) - SMPProxyFallback.Prohibit -> ValueTitleDesc(SMPProxyFallback.Prohibit, generalGetString(MR.strings.network_smp_proxy_fallback_prohibit), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_fallback_prohibit_description), density)) - } - } - } - - SectionItemWithValue( - generalGetString(MR.strings.network_smp_proxy_fallback_allow_downgrade), - smpProxyFallback, - values, - icon = painterResource(MR.images.ic_arrows_left_right), - enabled = enabled, - onSelected = { - showModal { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { - AppBarTitle(stringResource(MR.strings.network_smp_proxy_fallback_allow_downgrade)) - SectionViewSelectableCards(null, smpProxyFallback, values, updateSMPProxyFallback) - } - } - } - ) -} - -@Composable -private fun NetworkSectionFooter(revert: () -> Unit, save: () -> Unit, revertDisabled: Boolean, saveDisabled: Boolean) { - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - FooterButton(painterResource(MR.images.ic_replay), stringResource(MR.strings.network_options_revert), revert, revertDisabled) - FooterButton(painterResource(MR.images.ic_check), stringResource(MR.strings.network_options_save), save, saveDisabled) - } -} - // https://stackoverflow.com/a/106223 private fun validHost(s: String): Boolean { val validIp = Regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])[.]){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") @@ -652,7 +416,7 @@ fun validPort(s: String): Boolean { return s.isNotBlank() && s.matches(validPort) } -private fun showUpdateNetworkSettingsDialog( +fun showUpdateNetworkSettingsDialog( title: String, startsWith: String = "", message: String = generalGetString(MR.strings.updating_settings_will_reconnect_client_to_all_servers), @@ -675,18 +439,8 @@ fun PreviewNetworkAndServersLayout() { SimpleXTheme { NetworkAndServersLayout( currentRemoteHost = null, - developerTools = true, networkUseSocksProxy = remember { mutableStateOf(true) }, - proxyPort = remember { mutableStateOf(9050) }, toggleSocksProxy = {}, - onionHosts = remember { mutableStateOf(OnionHosts.PREFER) }, - sessionMode = remember { mutableStateOf(TransportSessionMode.User) }, - smpProxyMode = remember { mutableStateOf(SMPProxyMode.Never) }, - smpProxyFallback = remember { mutableStateOf(SMPProxyFallback.Allow) }, - useOnion = {}, - updateSessionMode = {}, - updateSMPProxyMode = {}, - updateSMPProxyFallback = {}, ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 3dfe8c3590..b0aa365a21 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -112,7 +112,7 @@ fun SettingsLayout( Modifier .fillMaxSize() .themedBackground(theme.value.base) - .padding(top = if (appPlatform.isAndroid) DEFAULT_PADDING else DEFAULT_PADDING * 3) + .padding(top = if (appPlatform.isAndroid) DEFAULT_PADDING else DEFAULT_PADDING * 2.8f) ) { AppBarTitle(stringResource(MR.strings.your_settings)) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 734e08935d..8f1635369a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -773,7 +773,6 @@ أرشيف قاعدة البيانات القديمة غير مفعّل رابط دعوة لمرة واحدة - سوف تكون مضيفات البصل مطلوبة للاتصال. المراقب لا يوجد نص دور عضو جديد @@ -789,8 +788,6 @@ لا سوف تكون مضيفات البصل مطلوبة للاتصال. \nيُرجى ملاحظة: أنك لن تتمكن من الاتصال بالخوادم بدون عنوان onion. - سيتم استخدام مضيفات البصل عند توفرها. - لن يتم استخدام مضيفات البصل. اسم عرض جديد: عبارة مرور جديدة… قيد الانتظار @@ -892,7 +889,6 @@ حٌديثت السجل في حٌديثت السجل في: %s استعادة - إرجاع يرى المستلمون التحديثات أثناء كتابتها. استلمت، ممنوع حفظ @@ -1165,7 +1161,6 @@ ملفات تعريف الدردشة الخاصة بك عنوان SimpleX الخاص بك خوادم SMP الخاصة بك - هل تريد تحديث إعداد مضيفي onion.؟ عندما يكون التطبيق قيد التشغيل عبر المُرحل لقد انضممت إلى هذه المجموعة diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 68199c84af..6a6d8b120f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -707,6 +707,7 @@ Send us email SimpleX Lock Chat console + Message servers SMP servers Configured SMP servers Other SMP servers @@ -731,6 +732,8 @@ Delete server The servers for new connections of your current chat profile Save servers? + Update network settings? + Media & file servers XFTP servers Configured XFTP servers Other XFTP servers @@ -754,7 +757,8 @@ Save Network & servers Advanced network settings - Network settings + Advanced settings + SOCKS proxy SOCKS proxy settings Use SOCKS proxy port %d @@ -764,7 +768,6 @@ Access the servers via SOCKS proxy on port %d? Proxy must be started before enabling this option. Use direct Internet connection? If you confirm, the messaging servers will be able to see your IP address, and your provider - which servers you are connecting to. - Update .onion hosts setting? Use .onion hosts When available No @@ -772,9 +775,6 @@ Onion hosts will be used when available. Onion hosts will not be used. Onion hosts will be required for connection.\nPlease note: you will not be able to connect to the servers without .onion address. - Onion hosts will be used when available. - Onion hosts will not be used. - Onion hosts will be required for connection. Transport isolation Chat profile Connection @@ -785,7 +785,7 @@ Please note: message and file relays are connected via SOCKS proxy. Calls and sending link previews use direct connection.]]> Private routing Always - Unknown relays + Unknown servers Unprotected Never Always use private routing. @@ -1607,6 +1607,7 @@ Error saving group profile + TCP connection Reset to defaults sec TCP connection timeout @@ -1616,8 +1617,8 @@ PING interval PING count Enable TCP keep-alive - Revert Save + Save and reconnect Update network settings? Updating settings will re-connect the client to all servers. Update diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index 81f537133c..d43aee7c8a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -800,13 +800,11 @@ Мрежови настройки Порт порт %d - Няма се използват Onion хостове. Задължително Не За свързване ще са необходими Onion хостове. \nМоля, обърнете внимание: няма да можете да се свържете със сървърите без .onion адрес. Ще се използват Onion хостове, когато са налични. - Ще се използват Onion хостове, когато са налични. Няма се използват Onion хостове. Нека да поговорим в SimpleX Chat Парола за показване @@ -878,7 +876,6 @@ Няма избран чат Известия Известията ще спрат да работят, докато не стартирате отново приложението - За свързване ще са необходими Onion хостове. Само 10 изображения могат да бъдат изпратени едновременно Само собствениците на групата могат да активират файлове и медията. Само собствениците на групата могат да активират гласови съобщения. @@ -1147,7 +1144,6 @@ Високоговорителят е изключен Спрете чата, за да активирате действията с базата данни. Роля - Отмени промените Запази Нулирай цветовете Вторичен @@ -1225,7 +1221,6 @@ Вашите SMP сървъри Използвай SOCKS прокси Използвай SOCKS прокси\? - Актуализиране на настройката за .onion хостове\? Използване на директна интернет връзка\? Използвай .onion хостове Когато са налични diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 5a73805093..e64ea5cc8e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -228,7 +228,6 @@ Použít proxy server SOCKS\? Použít přímé připojení k internetu\? Ne - Onion hostitelé nebudou použiti. Chat profil Připojení simplexmq: v%s (%2s) @@ -357,7 +356,6 @@ Nebyl vybrán žádný kontakt Snažíte se pozvat kontakt, se kterým jste sdíleli inkognito profil, do skupiny, ve které používáte svůj hlavní profil Skupina - Vrátit Aktualizací nastavení se klient znovu připojí ke všem serverům. Nastavit 1 den Chyba spojení (AUTH) @@ -629,14 +627,11 @@ Ujistěte se, že adresy serverů WebRTC ICE jsou ve správném formátu, oddělené na řádcích a nejsou duplicitní. Uložit Síť a servery - Aktualizovat nastavení hostitelů .onion\? Použít hostitele .onion Když bude dostupný Povinné Onion hostitelé budou použiti, pokud jsou k dispozici. Onion hostitelé nebudou použiti. - Onion hostitelé budou použiti, pokud jsou k dispozici. - Pro připojení budou vyžadováni Onion hostitelé. Izolace přenosu for each chat profile you have in the app.]]> Oddělit TCP připojení (a SOCKS pověření) bude použito pro všechny kontakty a členy skupin. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 1dd525124c..0d68c4b626 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -396,7 +396,6 @@ Zugriff auf die Server über SOCKS-Proxy auf Port %d? Der Proxy muss gestartet werden, bevor diese Option aktiviert wird. Direkte Internetverbindung verwenden? Wenn Sie dies bestätigen, können die Messaging-Server Ihre IP-Adresse sowie Ihren Provider sehen und mit welchen Servern Sie sich verbinden. - Einstellung für .onion-Hosts aktualisieren? Verwende .onion-Hosts Wenn verfügbar Nein @@ -405,9 +404,6 @@ Onion-Hosts werden nicht verwendet. Für die Verbindung werden Onion-Hosts benötigt. \nBitte beachten Sie: Ohne .onion-Adresse können Sie keine Verbindung mit den Servern herstellen. - Wenn Onion-Hosts verfügbar sind, werden sie verwendet. - Onion-Hosts werden nicht verwendet. - Für die Verbindung werden Onion-Hosts benötigt. Erscheinungsbild Adresse erstellen @@ -826,7 +822,6 @@ Protokollzeitüberschreitung PING-Intervall TCP-Keep-Alive aktivieren - Zurücksetzen Speichern Netzwerkeinstellungen aktualisieren? Die Aktualisierung der Einstellungen wird den Client wieder mit allen Servern verbinden. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 836ab0865d..cd67a28477 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -447,7 +447,6 @@ Asegúrate de que las direcciones del servidor SMP tienen el formato correcto, están separadas por líneas y no están duplicadas. Notificación instantánea Configuración de red - No se usarán hosts .onion cifrado de extremo a extremo de 2 capas .]]> Puedes cambiar estos ajustes más tarde en Configuración. Instantánea @@ -466,7 +465,6 @@ Contacto y texto MIEMBRO nunca - Se requieren hosts .onion para la conexión No se usarán hosts .onion Vista previa de notificaciones ¡Invitación caducada! @@ -511,7 +509,6 @@ Asegúrate de que las direcciones del servidor WebRTC ICE tienen el formato correcto, están separadas por líneas y no duplicadas. Se requieren hosts .onion para la conexión \nRecuerda: no podrás conectarte a servidores que no tengan dirección .onion. - Se usarán hosts .onion si están disponibles. Inmune a spam y abuso si SimpleX no tiene identificadores de usuario, ¿cómo puede entregar los mensajes\?]]> Videollamada entrante @@ -636,7 +633,6 @@ Guardar y notificar contactos Protocolo y código abiertos: cualquiera puede usar los servidores. Rol - Revertir Intervalo PING Contador PING Sólo tu contacto puede eliminar mensajes de forma irreversible (tu puedes marcarlos para eliminar). (24 horas) @@ -800,7 +796,6 @@ Estrella en GitHub Lista de servidores para las conexiones nuevas de tu perfil actual ¿Usar conexión directa a Internet\? - ¿Actualizar la configuración de los hosts .onion\? El perfil sólo se comparte con tus contactos. inicializando… Mensajes omitidos diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml index c18c163483..2fe4ec452e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -588,7 +588,6 @@ میزبان‌های Onion برای اتصال الزامی خواهد بود. \nلطفا توجه داشته باشید: شما بدون نشانی‌ onion. قادر نخواهید بود به سرورها متصل شوید. ذخیره - تنظیمات میزبان‌های onion. به روز شود؟ پورت از پروکسی SOCKS استفاده شود؟ از اتصال مستقیم اینترنت استفاده شود؟ @@ -597,7 +596,6 @@ هاست الزامی از میزبان‌های Onion وقتی موجود باشند استفاده خواهد شد. - از میزبان‌های Onion وقتی موجود باشند استفاده خواهد شد. ظاهر نسخه برنامه: v%s نمایش گزینه‌های توسعه‌دهنده @@ -608,7 +606,6 @@ برای هر نمایه گپی که در برنامه دارید استفاده خواهد شد.]]> نمایش خطاهای داخلی نمایش تماس‌های کند API - از میزبان‌های Onion استفاده نخواهد شد. خیر استفاده از میزبان‌های onion. را روی «خیر» تنظیم کنید اگر پروکسی SOCKS از آنها پشتیبانی نمی‌کند.]]> سفارشی کردن تم @@ -630,7 +627,6 @@ اتصال ساختار برنامه: %s تنظیمات شبکه - میزبان‌های Onion برای اتصال الزامی خواهد بود. نمایش: لطفا توجه داشته باشید: واسطه‌های پیام و پرونده از طریق پروکسی SOCKS متصل می‌شوند. تماس‌ها و ارسال پیش‌نمایش‌های لینک از اتصال مستقیم استفاده می‌کنند.]]> گزینه‌های توسعه‌دهنده @@ -1313,7 +1309,6 @@ وقفه پینگ شمار پینگ فعال کردن زنده نگه‌داشتن TCP - برگشت ذخیره تنظیمات شبکه به‌روزرسانی شود؟ افزودن نمایه diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index b89f0ddb62..8200d8a0ff 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -623,7 +623,6 @@ Aseta kontaktin nimi… Tallenna salasana Keystoreen Vain kontaktisi voi lähettää katoavia viestejä. - Onion-isäntiä käytetään, kun niitä on saatavilla. Portti portti %d Use .onion hosts arvoon Ei, jos SOCKS-välityspalvelin ei tue niitä.]]> @@ -661,7 +660,6 @@ TCP-yhteyden aikakatkaisu PING-määrä PING-väli - Palauta Profiili- ja palvelinyhteydet Aseta ryhmän asetukset jos SimpleX ei sisällä käyttäjätunnuksia, kuinka se voi toimittaa viestejä\?]]> @@ -916,8 +914,6 @@ Kiitos SimpleX Chatin asentamisesta! Asetukset Lisää - Onion-isäntiä ei käytetä. - Yhteyden muodostamiseen tarvitaan Onion-isäntiä. Yksityiset ilmoitukset Kaiutin pois päältä Kaiutin päällä @@ -1156,7 +1152,6 @@ \nkontakteillesi ICE-palvelimesi Kun saatavilla - Päivitä .onion-isäntien asetus\? Käytä .onion-isäntiä Puhelusi Tätä ryhmää ei enää ole olemassa. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 2d0e366647..eb2c76d2bb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -321,8 +321,6 @@ Assurez-vous que les adresses des serveurs WebRTC ICE sont au bon format et ne sont pas dupliquées, un par ligne. Accéder aux serveurs via un proxy SOCKS sur le port %d \? Le proxy doit être démarré avant d\'activer cette option. Utiliser les hôtes .onions - Les hôtes .onion seront utilisés lorsqu\'ils sont disponibles. - Les hôtes .onion seront nécessaires pour la connexion. transmettre ainsi que par quel·s serveur·s vous pouvez recevoir les messages de vos contacts.]]> Vos paramètres SimpleX Lock @@ -414,12 +412,10 @@ Comment faire Serveurs ICE (un par ligne) Erreur lors de la sauvegarde des serveurs ICE - Mettre à jour le paramètre des hôtes .onion \? Quand disponible Les hôtes .onion ne seront pas utilisés. Les hôtes .onion seront nécessaires pour la connexion. \nAttention : vous ne pourrez pas vous connecter aux serveurs sans adresse .onion. - Les hôtes .onion ne seront pas utilisés. Supprimer l\'adresse \? Tous vos contacts resteront connectés. Partager le lien @@ -833,7 +829,6 @@ directe Entièrement décentralisé – visible que par ses membres. Les membres du groupes peuvent envoyer des messages éphémères. - Revenir en arrière Interdire l’envoi de messages éphémères. Le mode incognito protège votre vie privée en utilisant un nouveau profil aléatoire pour chaque contact. La mise à jour des ces paramètres reconnectera le client à tous les serveurs. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index d654b5135f..ccc0cc3593 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -740,9 +740,7 @@ Új tag szerepköre Kikapcsolva Érvénytelen hivatkozás! - A kapcsolódáshoz Onion kiszolgálókra lesz szükség. Változások a %s verzióban - Onion kiszolgálók használata, ha azok rendelkezésre állnak. Érvénytelen kiszolgálócím! k soha @@ -765,7 +763,6 @@ bekapcsolva Japán és portugál kezelőfelület Az üzenetek végleges törlése le van tiltva ebben a csoportban. - Onion kiszolgálók nem lesznek használva. %s eszközzel megszakadt a kapcsolat]]> hónap Üzenetvázlat @@ -1098,7 +1095,6 @@ Hívások nem sikerült elküldeni KEZELŐFELÜLET SZÍNEI - Visszaállítás Előző jelszó megadása az adatbázis biztonsági mentésének visszaállítása után. Ez a művelet nem vonható vissza. Másodlagos SOCKS PROXY @@ -1432,7 +1428,6 @@ Ismeretlen adatbázis hiba: %s Elrejtheti vagy lenémíthatja a felhasználó profiljait - koppintson (vagy asztali alkalmazásban kattintson) hosszan a profilra a felugró menühöz. Inkognító mód kapcsolódáskor. - Tor .onion kiszolgálók beállításainak frissítése? Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait. Csatlakozott ehhez a csoporthoz %1$s csoporthoz!]]> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index a1ee64ed62..ce7396809b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -610,18 +610,14 @@ No Gli host Onion saranno necessari per la connessione. \nNota bene: non potrai connetterti ai server senza indirizzo .onion . - Gli host Onion saranno necessari per la connessione. Gli host Onion verranno usati quando disponibili. - Gli host Onion verranno usati quando disponibili. Gli host Onion non verranno usati. - Gli host Onion non verranno usati. Valuta l\'app Obbligatorio Salva I server WebRTC ICE salvati verranno rimossi. Condividi link Dai una stella su GitHub - Aggiornare l\'impostazione degli host .onion\? Usare una connessione internet diretta\? Usa gli host .onion Usare il proxy SOCKS\? @@ -816,7 +812,6 @@ Scadenza del protocollo Ricezione via Ripristina i predefiniti - Ripristina Salva Salva il profilo del gruppo sec diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index 2fe340617c..3c33a8cc88 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -604,8 +604,6 @@ בהיר ודאו שלקובץ יש תחביר YAML תקין. ייצאו ערכת נושא כדי לקבל דוגמה למבנה תקין של קובץ ערכת נושא. ככל הנראה איש קשר זה מחק את החיבור איתך. - ייעשה שימוש במארחי Onion כאשר יהיו זמינים. - מארחי Onion יידרשו לחיבור. נחסם על ידי %s אין קוד גישה לאפליקציה ניתן לשלוח רק 10 סרטונים בו־זמנית @@ -674,7 +672,6 @@ יידרשו מארחי onion לחיבור. \nשימו לב: לא תוכלו להתחבר לשרתים ללא כתובת .onion. לא ייעשה שימוש במארחי Onion. - לא ייעשה שימוש במארחי Onion. שיחה שלא נענתה הצפנה מקצה־לקצה דו־שכבתית.]]> ללא הצפנה מקצה־לקצה @@ -902,7 +899,6 @@ שניות אישור תפקיד - ביטול שמור ועדכן את פרופיל הקבוצה לשמור הודעת פתיחה\? %s (נוכחי) @@ -1077,7 +1073,6 @@ (כדי לשתף עם איש הקשר שלך) כדי להתחיל צ׳אט חדש השתמש עבור חיבורים חדשים - לעדכן הגדרות מארחי ‪.onion‬\?‬ כדי לאמת הצפנה מקצה־לקצה עם איש הקשר שלכם, יש להשוות (או לסרוק) את הקוד במכשירים שלכם. פרופילי צ׳אט לעדכן מצב בידוד תעבורה\? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 7d97b6fa03..38df6a8b16 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -204,7 +204,6 @@ 接続済み リンク経由で繋がる。 接続エラー - 接続にオニオンのホストが必要となります。 接続待ち (紹介済み) 接続エラー (AUTH) 接続タイムアウト @@ -222,7 +221,6 @@ 追加情報アイコン オニオンのホストが利用可能時に使われます。 オニオンのホストが使われません。 - オニオンのホストが利用可能時に使われます。 画像を1回で最大10枚を送信できます。 2層エンドツーエンド暗号化で送信されたプロフィール、連絡先、グループ、メッセージは、クライント端末にしか保存されません。]]> グループ設定を変えられるのはグループのオーナーだけです。 @@ -315,7 +313,6 @@ ICEサーバ (1行に1サーバ) ネットワークとサーバ ネットワーク設定 - オニオンのホストが使われません。 アドレスを削除 保存せずに閉じる 表示の名前には空白が使用できません。 @@ -735,7 +732,6 @@ あなたのICEサーバ 直接にインタネットに繋がりますか? SOCKSプロキシを使いますか? - .onionのホスト設定を更新しますか? .onionホストを使う 利用可能時に トランスポート隔離 @@ -883,7 +879,6 @@ アプリ起動時にパスフレーズを入力しなければなりません。端末に保存されてません。 データベースのパスフレーズ変更が完了してません。 リンク、またはQRコードを共有できます。誰でもグループに参加できます。後で削除しても、グループのメンバーがそのままのこります。 - 元に戻す 更新 1日に設定 一定時間が経ったら送信されたメッセージが削除されます。 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index 9d7088d742..ffd62b4755 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -635,7 +635,6 @@ 이 설정은 현재 내 프로필의 메시지에 적용되어요. 멤버 역할이 \"%s\"(으)로 변경되고, 회원은 새로운 초대를 받게 될 거예요. - 되돌리기 이 채팅에서는 메시지 영구 삭제가 허용되지 않았어요. 나가기 큰 파일! @@ -667,9 +666,6 @@ 사용 가능한 경우 Onion 호스트가 사용될 거예요. Onion 호스트가 사용되지 않을 거예요. 전송 격리 - Onion 호스트가 사용되지 않을 거예요. - 사용 가능한 경우 Onion 호스트가 사용될 거예요. - 연결하려면 Onion 호스트가 필요해요. 차세대 사생활 보호 메시징 새 비밀번호… TCP 연결 유지 활성화 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index a98aba3ff6..6139eb5133 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -130,7 +130,6 @@ grupės profilis atnaujintas Grupė Ištrinti pokalbio profilį\? - Sugrąžinti Įrašyti įjungta Ištrinti po @@ -1373,7 +1372,6 @@ (nuskanuokite ar įklijuokite iš iškarpinės) Priėmėte prisijungimą Jūs pakvietėte kontaktą - Onion serveriai bus reikalingi ryšiui. Onion serveriai bus naudojami, kai tik bus. Išeiti neišsaugant Jūs kontroliuojate savo pokalbį! @@ -1565,7 +1563,6 @@ Jūsų SimpleX adresas Nuskanuoti serverio QR kodą Reikalingi - Onion serveriai bus naudojami, kai tik bus. Onion serveriai nebus naudojami. Savaiminis susinaikinimas Senas duomenų bazės archyvas @@ -1628,8 +1625,6 @@ Moderuoti nori prisijungti prie jūsų! nuorodos peržiūros nuotrauka - Onion serveriai nebus naudojami. - Atnaujinti .onion serverių nustatymą? Kai programėlė yra paleista Užrakto režimas Galite paleisti pokalbius per programėlės nustatymus/ duomenų bazę arba paleisdami programėlę iš naujo. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml index b807789c08..d97cf4b7ad 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml @@ -320,7 +320,6 @@ സ്വീകരിക്കുന്ന വിലാസം മാറുക സെർവറുകൾ സംരക്ഷിക്കുക - പഴയപടിയാക്കുക സംവിധാനം സംവിധാനം ശീർഷകം diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 349fd766f6..404ea1fe28 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -546,7 +546,6 @@ Eenmalige uitnodiging link Plakken Vooraf ingesteld server adres - Onion hosts worden niet gebruikt. Privé meldingen Plak de link die je hebt ontvangen Periodiek @@ -573,7 +572,6 @@ Wachtwoord is nodig uit Eenmalige uitnodiging link - Onion hosts zijn vereist voor verbinding. Alleen groep eigenaren kunnen groep voorkeuren wijzigen. (alleen opgeslagen door groepsleden) Vooraf ingestelde server @@ -584,7 +582,6 @@ OK Onion hosts zijn vereist voor verbinding. Onion hosts worden gebruikt indien beschikbaar. - Onion hosts worden gebruikt indien beschikbaar. Onion hosts worden niet gebruikt. Open-source protocol en code. Iedereen kan de servers draaien. Mensen kunnen alleen verbinding met u maken via de links die u deelt. @@ -808,7 +805,6 @@ Resetten naar standaardwaarden Ontvangst adres wijzigen Protocol timeout - Terugdraaien Opslaan sec Wanneer je een incognito profiel met iemand deelt, wordt dit profiel gebruikt voor de groepen waarvoor ze je uitnodigen. @@ -904,7 +900,6 @@ verbinding maken met SimpleX Chat ontwikkelaars om vragen te stellen en updates te ontvangen.]]> Tenzij uw contact de verbinding heeft verwijderd of deze link al is gebruikt, kan het een bug zijn. Meld het alstublieft. \nOm verbinding te maken, vraagt u uw contact om een andere verbinding link te maken en te controleren of u een stabiele netwerkverbinding heeft. - .onion hosts-instelling updaten\? SimpleX Chat servers gebruiken\? Spraak berichten zijn verboden in deze groep. Welkom %1$s! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 2422148b45..dc97062155 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -348,14 +348,11 @@ Nie Hosty onion będą wymagane do połączenia. \nUwaga: nie będziesz mógł połączyć się z serwerami bez adresu .onion. - Hosty onion będą wymagane do połączenia. Hosty onion będą używane, gdy będą dostępne. - Hosty onion będą używane, gdy będą dostępne. Hosty onion nie będą używane. Wymagane Zapisz Izolacja transportu - Zaktualizować ustawienie hostów .onion\? Zaktualizować tryb izolacji transportu\? Użyć bezpośredniego połączenia z Internetem\? Użyj hostów .onion @@ -758,7 +755,6 @@ Profil i połączenia z serwerem Limit czasu protokołu Przywróć wartości domyślne - Przywrócić Zapisz sek Dotknij, aby aktywować profil. @@ -953,7 +949,6 @@ Wideo zaproponował %s: %2s Tylko właściciele grup mogą włączyć wiadomości głosowe. - Hosty onion nie będą używane. Tylko Twój kontakt może nieodwracalnie usunąć wiadomości (możesz oznaczyć je do usunięcia). (24 godziny) Hasło nie zostało znalezione w Keystore, wprowadź je ręcznie. Może się tak zdarzyć, gdy przywrócisz dane aplikacji za pomocą narzędzia do kopii zapasowych. Jeśli tak nie jest, skontaktuj się z programistami. Członkowie grupy mogą wysyłać znikające wiadomości. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index f0a0c53496..2fa9f85599 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -482,7 +482,6 @@ Arquivo de banco de dados antigo Convidar membros Nenhum contato selecionado - Reverter Salvar Redefinir cores interface italiana @@ -647,7 +646,6 @@ Onion hosts não serão usados. Os hosts Onion serão necessários para a conexão. \nAtenção: você não será capaz de se conectar aos servidores sem um endereço .onion - Os hosts Onion serão necessários para a conexão. Versão principal: v%s repositório do GitHub.]]> Pode ser mudado mais tarde via configurações. @@ -761,9 +759,7 @@ Proteja seus perfis de bate-papo com uma senha! Este texto está disponível nas configurações Escanear código - Hosts Onion não serão usados. Os hosts Onion serão usados quando disponíveis. - Os hosts Onion serão usados quando disponíveis. Seu perfil atual Privacidade redefinida Notificações privadas @@ -963,7 +959,6 @@ Compartilhar mensagem… Bem-vindo(a) %1$s! você está convidado para o grupo - Atualizar configuração de hosts .onion\? Usar bate-papo Mensagens de voz são proibidas neste chat. Vídeo diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index 9f5e81f4da..e652ab27e5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -578,7 +578,6 @@ Pré-visualização de notificação Muito provavelmente este contato eliminou a conexão consigo. Este texto está disponível nas definições - Atualizar definições de servidores .onion\? Pode ser alterado mais tarde através das definições. AJUDA SUPORTE SIMPLEX CHAT @@ -613,7 +612,6 @@ Para receber notificações, por favor, digite a senha da base de dados Hosts Onion não serão usados. Hosts Onion serão usados quando disponíveis. - Hosts Onion serão necessários para a conexão. chamada de vídeo (sem encriptação ponta a ponta) chamada de áudio (não encriptada ponta a ponta) chamada de áudio encriptada ponta a ponta @@ -623,7 +621,6 @@ oferecido %s Arquivo de base de dados antigo Hosts Onion serão necessários para a conexão. - Hosts Onion serão usados quando disponíveis. Pode acontecer quando você ou sua conexão usaram o backup de base de dados antigo. encriptado ponta a ponta desligado @@ -644,7 +641,6 @@ Para verificar a encriptação de ponta a ponta com o seu contato, compare (ou leia) o código nos seus dispositivos. Ler o código de segurança a partir da aplicação do seu contacto. Ler o código QR do servidor - Hosts Onion não serão usados. encriptação de ponta a ponta de 2 camadas.]]> o contacto tem encriptação ponta a ponta sem encriptação ponta a ponta diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml index 4c8865f464..7c509a9e1f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml @@ -193,7 +193,6 @@ Respinge Salvează fraza de acces în setări Salvează și actualizează profilul grupului - Revenire Repetă cererea de alăturare? Repornește conversația salvat diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 0e5b329669..6482549f81 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -394,7 +394,6 @@ Соединяться с серверами через SOCKS прокси через порт %d? Прокси должен быть запущен до включения этой опции. Использовать прямое соединение с Интернет? Если Вы подтвердите, серверы смогут видеть Ваш IP адрес, а провайдер - с какими серверами Вы соединяетесь. - Обновить настройки .onion хостов? Использовать .onion хосты Когда возможно Нет @@ -403,9 +402,6 @@ Onion хосты не используются. Подключаться только к onion хостам. \nОбратите внимание: Вы не сможете соединиться с серверами, у которых нет .onion адреса. - Onion хосты используются, если возможно. - Onion хосты не используются. - Подключаться только к onion хостам. Интерфейс Создать адрес @@ -831,7 +827,6 @@ Таймаут протокола Интервал PING Включить TCP keep-alive - Отменить изменения Сохранить Обновить настройки сети? Обновление настроек приведет к переподключению клиента ко всем серверам. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml index ef020efb54..058f0305a8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml @@ -665,10 +665,7 @@ การตั้งค่าเครือข่าย จำเป็นต้องมีโฮสต์หัวหอมสำหรับการเชื่อมต่อ ไม่ - จำเป็นต้องมีโฮสต์หัวหอมสำหรับการเชื่อมต่อ - โฮสต์หัวหอมจะถูกใช้เมื่อมี โฮสต์หัวหอมจะไม่ถูกใช้ - โฮสต์หัวหอมจะไม่ถูกใช้ รหัสผ่านที่จะแสดง สายที่ไม่ได้รับ ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น @@ -852,7 +849,6 @@ บันทึกและอัปเดตโปรไฟล์กลุ่ม กำลังรับผ่าน รีเซ็ตเป็นค่าเริ่มต้น - เปลี่ยนกลับ บันทึก รีเซ็ตสี ได้รับ, ห้าม @@ -1147,7 +1143,6 @@ ใช้พร็อกซี SOCKS ใช้การเชื่อมต่ออินเทอร์เน็ตโดยตรงหรือไม่\? ใช้พร็อกซี SOCKS หรือไม่\? - อัปเดตการตั้งค่าโฮสต์ .onion ไหม\? ใช้โฮสต์ .onion เมื่อพร้อมใช้งาน อัปเดตโหมดการแยกการขนส่งไหม\? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 1d67d2a0a9..5413afa82b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -1145,7 +1145,6 @@ Bağlantı paylaş SimpleX Ekibi %s, %s ve %s bağlandı - Geri al SOCKS VEKİLİ Masaüstür cihazlar SMP sunucuları @@ -1318,14 +1317,12 @@ Kayıt %s te güncellendi Uygulama yeni yerel dosyaları şifreler (videolar dışında). %s , %s de - Bağlantı için Onion ana bilgisayarları gerekli olacaktır. Alıcılar devre dışı bırakılsın mı? Bağlantı yeniden senkronizasyonunu şifrele? Grup ayarlarından ve kişilerden geçersiz kılınmış olabilirler Yeni üyelere geçmiş gönderilmedi. Güvenlik değerlendirmesi Yeniden dene - Onion ana bilgisayarları mümkün olduğunda kullanılacaktır. Sunuculara %d bağlantı noktasındaki vekil SOCKS aracılığıyla erişilsin mi? Vekil bu seçeneği etkinleştirmeden önce başlatılmak zorundadır. İstenmeyen mesajları gizlemek için. Test %s adımında hata yaşandı. @@ -1362,7 +1359,6 @@ Ayarlardaki parola silinsin mi? Alıcılar etkinleştirilsin mi? Yeni bir sohbet başlatmak için tıkla - Onion ana bilgisayarları kullanılmayacaktır. Kişinin engelini kaldır Yönlendirici sunucusu sadece lazım ise kullanılacak. Diğer taraf IP adresini görebilir. %s ın bağlantısı kesildi]]> @@ -1510,7 +1506,6 @@ Telefona bağlandı Bağlanırken takma ada geçiş yap. Mesaj gönderildi! - .onion ana bilgisayarları ayarı güncellensin mi? Yeni bağlantılar için kullan senin için adres değiştirildi SOCKS vekilini kullan? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index abdf0dd8e0..7b6087e113 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -251,7 +251,6 @@ Налаштування мережі Використовувати SOCKS-проксі? Використовувати прямий підключення до Інтернету? - Оновити налаштування .onion-хостів? Використовувати .onion-хости Якщо доступно Ні @@ -640,8 +639,6 @@ Помилка збереження серверів ICE .Onion-хости будуть обов\'язковими для підключення. \nЗверніть увагу: ви не зможете підключитися до серверів без адреси .onion. - Хости .onion будуть використовуватися, якщо доступні. - Хости .onion будуть обов\'язковими для підключення. Показати параметри розробника Ідентифікатори бази даних та опція ізоляції транспорту. Сповіщення перестануть працювати, поки ви не перезапустите додаток @@ -953,7 +950,6 @@ зображення попереднього перегляду посилання скасувати попередній перегляд посилання Налаштування - Хости .onion не будуть використовуватися. Виклик у процесі Помилка бази даних Відновити @@ -997,7 +993,6 @@ ДЛЯ КОНСОЛІ Учасника буде вилучено з групи - цю дію неможливо скасувати! Змінити роль - Відновити Ви все ще отримуватимете дзвінки та сповіщення від приглушених профілів, коли вони активні. %d місяці %dmth diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 569567c4ed..750d640028 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -667,7 +667,6 @@ 关闭 连接需要 Onion 主机。 \n请注意:如果没有 .onion 地址,您将无法连接到服务器。 - Onion 主机将在可用时使用。 从不 已提供 %s 已提供 %s:%2s @@ -686,8 +685,6 @@ 没有联系人可添加 网络状态 关闭 - 将不会使用 Onion 主机。 - 连接需要 Onion 主机。 没有收到或发送的文件 发送人已取消文件传输。 分享 @@ -744,7 +741,6 @@ 开始新的聊天 要与您的联系人验证端到端加密,请比较(或扫描)您设备上的代码。 取消静音 - 更新 .onion 主机设置? 更新传输隔离模式? (从剪贴板扫描或粘贴) 保护队列 @@ -829,7 +825,6 @@ 已删除 角色 - 恢复 重置颜色 减少电池使用量 为了保护时区,图像/语音文件使用 UTC。 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 21298376d9..5935ea303c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -412,8 +412,6 @@ ICE 伺服器(每行一個) 使用 .onion 主機 Onion 主機不會啟用。 - Onion 主機會在可用時啟用。 - Onion 主機不會啟用。 連接 刪除聯絡地址 顏色 @@ -437,7 +435,6 @@ 請確保你的 WebRTC ICE 伺服器地址是正確的格式,每行也有分隔和沒有重複。 使用直接互聯網連接? 如果你確定,你的訊息伺服器能夠看到你的 IP 位置,和你的網路供應商 - 你正在連接到哪些伺服器。 - 更新 .onion 主機設定? 刪除圖片 開始中 … 等待對方回應… @@ -496,7 +493,6 @@ 需要 Onion 主機會在可用時啟用。 - 連接時將需要使用 Onion 主機。 刪除聯絡地址? 你的個人檔案只會儲存於你的裝置和只會分享給你的聯絡人。 SimpleX 伺服器並不會看到你的個人檔案。 儲存並通知你的聯絡人 @@ -825,7 +821,6 @@ 自動銷毀訊息於這個群組內是禁用的。 已提供 %s 儲存群組檔案時出錯 - 恢復 主題 你允許 修改群組內的設定 From 5a42c0c1d2c1f5143f1d1192f64454f3385ab600 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 6 Aug 2024 23:37:55 +0900 Subject: [PATCH 2/4] android, desktop: show database import/export errors (#4601) * android, desktop: show database import/export errors * no line --- .../common/views/database/DatabaseView.kt | 42 ++++++++++++++++--- .../views/migration/MigrateFromDevice.kt | 22 +++++++--- .../common/views/migration/MigrateToDevice.kt | 5 +-- .../commonMain/resources/MR/base/strings.xml | 7 +++- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 3ade14bdce..ecf2aaf371 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -522,9 +522,17 @@ private fun exportArchive( progressIndicator.value = true withLongRunningApi { try { - val archiveFile = exportChatArchive(m, null, chatArchiveName, chatArchiveTime, chatArchiveFile) + val (archiveFile, archiveErrors) = exportChatArchive(m, null, chatArchiveName, chatArchiveTime, chatArchiveFile) chatArchiveFile.value = archiveFile - saveArchiveLauncher.launch(archiveFile.substringAfterLast(File.separator)) + if (archiveErrors.isEmpty()) { + saveArchiveLauncher.launch(archiveFile.substringAfterLast(File.separator)) + } else { + showArchiveExportedWithErrorsAlert(generalGetString(MR.strings.chat_database_exported_save), archiveErrors) { + withLongRunningApi { + saveArchiveLauncher.launch(archiveFile.substringAfterLast(File.separator)) + } + } + } progressIndicator.value = false } catch (e: Error) { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_exporting_chat_database), e.toString()) @@ -539,7 +547,7 @@ suspend fun exportChatArchive( chatArchiveName: MutableState, chatArchiveTime: MutableState, chatArchiveFile: MutableState -): String { +): Pair> { val archiveTime = Clock.System.now() val ts = SimpleDateFormat("yyyy-MM-dd'T'HHmmss", Locale.US).format(Date.from(archiveTime.toJavaInstant())) val archiveName = "simplex-chat.$ts.zip" @@ -550,7 +558,7 @@ suspend fun exportChatArchive( controller.apiSaveAppSettings(AppSettings.current.prepareForExport()) } wallpapersDir.mkdirs() - m.controller.apiExportArchive(config) + val archiveErrors = m.controller.apiExportArchive(config) if (storagePath == null) { deleteOldArchive(m) m.controller.appPrefs.chatArchiveName.set(archiveName) @@ -559,7 +567,7 @@ suspend fun exportChatArchive( chatArchiveName.value = archiveName chatArchiveTime.value = archiveTime chatArchiveFile.value = archivePath - return archivePath + return archivePath to archiveErrors } private fun deleteOldArchive(m: ChatModel) { @@ -592,6 +600,28 @@ private fun importArchiveAlert( ) } +fun showArchiveImportedWithErrorsAlert(archiveErrors: List) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.chat_database_imported), + text = generalGetString(MR.strings.restart_the_app_to_use_imported_chat_database) + "\n\n" + generalGetString(MR.strings.non_fatal_errors_occured_during_import) + archiveErrorsText(archiveErrors)) +} + +fun showArchiveExportedWithErrorsAlert(description: String, archiveErrors: List, onConfirm: () -> Unit) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.chat_database_exported_title), + text = description + "\n\n" + generalGetString(MR.strings.chat_database_exported_not_all_files) + archiveErrorsText(archiveErrors), + confirmText = generalGetString(MR.strings.chat_database_exported_continue), + onConfirm = onConfirm + ) +} + +private fun archiveErrorsText(errs: List): String = "\n" + errs.map { + when (it) { + is ArchiveError.ArchiveErrorImport -> it.importError + is ArchiveError.ArchiveErrorFile -> "${it.file}: ${it.fileError}" + } +}.joinToString(separator = "\n") + private fun importArchive( m: ChatModel, importedArchiveURI: URI, @@ -621,7 +651,7 @@ private fun importArchive( } } else { operationEnded(m, progressIndicator) { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.chat_database_imported), text = generalGetString(MR.strings.restart_the_app_to_use_imported_chat_database) + "\n" + generalGetString(MR.strings.non_fatal_errors_occured_during_import)) + showArchiveImportedWithErrorsAlert(archiveErrors) } } } catch (e: Error) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt index 3cc5468bbb..c60723ee36 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt @@ -480,12 +480,13 @@ private fun MutableState.exportArchive() { withLongRunningApi { try { getMigrationTempFilesDirectory().mkdir() - val archivePath = exportChatArchive(chatModel, getMigrationTempFilesDirectory(), mutableStateOf(""), mutableStateOf(Instant.DISTANT_PAST), mutableStateOf("")) - val totalBytes = File(archivePath).length() - if (totalBytes > 0L) { - state = MigrationFromState.DatabaseInit(totalBytes, archivePath) + val (archivePath, archiveErrors) = exportChatArchive(chatModel, getMigrationTempFilesDirectory(), mutableStateOf(""), mutableStateOf(Instant.DISTANT_PAST), mutableStateOf("")) + if (archiveErrors.isEmpty()) { + uploadArchive(archivePath) } else { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.migrate_from_device_exported_file_doesnt_exist)) + showArchiveExportedWithErrorsAlert(generalGetString(MR.strings.chat_database_exported_migrate), archiveErrors) { + uploadArchive(archivePath) + } state = MigrationFromState.UploadConfirmation } } catch (e: Exception) { @@ -498,6 +499,17 @@ private fun MutableState.exportArchive() { } } +private fun MutableState.uploadArchive(archivePath: String) { + val totalBytes = File(archivePath).length() + if (totalBytes > 0L) { + state = MigrationFromState.DatabaseInit(totalBytes, archivePath) + } else { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.migrate_from_device_exported_file_doesnt_exist)) + state = MigrationFromState.UploadConfirmation + } + +} + suspend fun initTemporaryDatabase(tempDatabaseFile: File, netCfg: NetCfg): Pair? { val (status, ctrl) = chatInitTemporaryDatabase(tempDatabaseFile.absolutePath) showErrorOnMigrationIfNeeded(status) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index bfb5c350a5..1a1e8426d4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -597,10 +597,7 @@ private fun MutableState.importArchive(archivePath: String, n val config = ArchiveConfig(archivePath, parentTempDirectory = databaseExportDir.toString()) val archiveErrors = controller.apiImportArchive(config) if (archiveErrors.isNotEmpty()) { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.chat_database_imported), - generalGetString(MR.strings.non_fatal_errors_occured_during_import) - ) + showArchiveImportedWithErrorsAlert(archiveErrors) } state = MigrationToState.Passphrase("", netCfg) MigrationToDeviceState.save(MigrationToDeviceState.Passphrase(netCfg)) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 6a6d8b120f..8c7b772089 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1196,7 +1196,7 @@ Error importing chat database Chat database imported Restart the app to use imported chat database. - Some non-fatal errors occurred during import - you may see Chat console for more details. + Some non-fatal errors occurred during import: Delete chat profile? This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Chat database deleted @@ -1222,6 +1222,11 @@ This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. Delete messages Error changing setting + Chat database exported + You may save the exported archive. + You may migrate the exported database. + Some file(s) were not exported + Continue Save passphrase in Keystore From f6ee6338c4423bbecabe4a730690fd5d978d361f Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 7 Aug 2024 00:12:30 +0900 Subject: [PATCH 3/4] android: status bar color fix in non-oneHandUI (#4603) --- .../kotlin/chat/simplex/common/App.kt | 4 ++- .../common/views/chatlist/ChatListView.kt | 25 +++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index b24c05937d..0df2633610 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -276,7 +276,9 @@ fun AndroidScreen(settingsState: SettingsViewState) { snapshotFlow { ModalManager.center.modalCount.value > 0 } .filter { chatModel.chatId.value == null } .collect { modalBackground -> - if (modalBackground && !chatModel.newChatSheetVisible.value) { + if (chatModel.newChatSheetVisible.value) { + platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, false, appPrefs.oneHandUI.get()) + } else if (modalBackground) { platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, false, false) } else { platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, !appPrefs.oneHandUI.get(), appPrefs.oneHandUI.get()) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 7b7c07bef5..310d4d163d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -47,13 +47,24 @@ private fun showNewChatSheet(oneHandUI: State) { ModalManager.start.closeModals() ModalManager.end.closeModals() chatModel.newChatSheetVisible.value = true - ModalManager.start.showModalCloseable( - closeOnTop = !oneHandUI.value, - ) { close -> - NewChatSheet(rh = chatModel.currentRemoteHost.value, close) - DisposableEffect(Unit) { - onDispose { - chatModel.newChatSheetVisible.value = false + ModalManager.start.showCustomModal { close -> + val close = { + // It will set it faster than in onDispose. It's important to catch the actual state before + // closing modal for reacting with status bar changes in [App] + chatModel.newChatSheetVisible.value = false + close() + } + ModalView(close, closeOnTop = !oneHandUI.value) { + if (appPlatform.isAndroid) { + BackHandler { + close() + } + } + NewChatSheet(rh = chatModel.currentRemoteHost.value, close) + DisposableEffect(Unit) { + onDispose { + chatModel.newChatSheetVisible.value = false + } } } } From 7441ed989275ccc992c531b1a4623883d501eb0c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 6 Aug 2024 16:13:36 +0100 Subject: [PATCH 4/4] core: choose random servers for the first user profile, use the same servers for other profiles (#4584) * core: choose random servers for the first user profile, use the same servers for other profiles * update ui clients --- apps/ios/Shared/Model/SimpleXAPI.swift | 4 +- apps/ios/SimpleXChat/APITypes.swift | 7 +-- .../chat/simplex/common/model/SimpleXAPI.kt | 9 ++- simplex-chat.cabal | 1 + src/Simplex/Chat.hs | 55 +++++++++++++------ src/Simplex/Chat/Controller.hs | 2 + src/Simplex/Chat/Core.hs | 2 +- src/Simplex/Chat/Terminal.hs | 2 + src/Simplex/Chat/Types.hs | 1 - tests/ChatTests/Direct.hs | 36 +----------- tests/RandomServers.hs | 51 +++++++++++++++++ tests/Test.hs | 2 + 12 files changed, 106 insertions(+), 66 deletions(-) create mode 100644 tests/RandomServers.hs diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 7ad8e38692..247e83c419 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -137,8 +137,8 @@ func apiGetActiveUser(ctrl: chat_ctrl? = nil) throws -> User? { } } -func apiCreateActiveUser(_ p: Profile?, sameServers: Bool = false, pastTimestamp: Bool = false, ctrl: chat_ctrl? = nil) throws -> User { - let r = chatSendCmdSync(.createActiveUser(profile: p, sameServers: sameServers, pastTimestamp: pastTimestamp), ctrl) +func apiCreateActiveUser(_ p: Profile?, pastTimestamp: Bool = false, ctrl: chat_ctrl? = nil) throws -> User { + let r = chatSendCmdSync(.createActiveUser(profile: p, pastTimestamp: pastTimestamp), ctrl) if case let .activeUser(user) = r { return user } throw r } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 570946155a..7fb89be92e 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -15,7 +15,7 @@ public let jsonEncoder = getJSONEncoder() public enum ChatCommand { case showActiveUser - case createActiveUser(profile: Profile?, sameServers: Bool, pastTimestamp: Bool) + case createActiveUser(profile: Profile?, pastTimestamp: Bool) case listUsers case apiSetActiveUser(userId: Int64, viewPwd: String?) case setAllContactReceipts(enable: Bool) @@ -156,8 +156,8 @@ public enum ChatCommand { get { switch self { case .showActiveUser: return "/u" - case let .createActiveUser(profile, sameServers, pastTimestamp): - let user = NewUser(profile: profile, sameServers: sameServers, pastTimestamp: pastTimestamp) + case let .createActiveUser(profile, pastTimestamp): + let user = NewUser(profile: profile, pastTimestamp: pastTimestamp) return "/_create user \(encodeJSON(user))" case .listUsers: return "/users" case let .apiSetActiveUser(userId, viewPwd): return "/_user \(userId)\(maybePwd(viewPwd))" @@ -1097,7 +1097,6 @@ public enum GroupLinkPlan: Decodable, Hashable { struct NewUser: Encodable, Hashable { var profile: Profile? - var sameServers: Bool var pastTimestamp: Bool } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 4e503244e7..3d66fea485 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -657,8 +657,8 @@ object ChatController { return null } - suspend fun apiCreateActiveUser(rh: Long?, p: Profile?, sameServers: Boolean = false, pastTimestamp: Boolean = false, ctrl: ChatCtrl? = null): User? { - val r = sendCmd(rh, CC.CreateActiveUser(p, sameServers = sameServers, pastTimestamp = pastTimestamp), ctrl) + suspend fun apiCreateActiveUser(rh: Long?, p: Profile?, pastTimestamp: Boolean = false, ctrl: ChatCtrl? = null): User? { + val r = sendCmd(rh, CC.CreateActiveUser(p, pastTimestamp = pastTimestamp), ctrl) if (r is CR.ActiveUser) return r.user.updateRemoteHostId(rh) else if ( r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.DuplicateName || @@ -2824,7 +2824,7 @@ class SharedPreference(val get: () -> T, set: (T) -> Unit) { sealed class CC { class Console(val cmd: String): CC() class ShowActiveUser: CC() - class CreateActiveUser(val profile: Profile?, val sameServers: Boolean, val pastTimestamp: Boolean): CC() + class CreateActiveUser(val profile: Profile?, val pastTimestamp: Boolean): CC() class ListUsers: CC() class ApiSetActiveUser(val userId: Long, val viewPwd: String?): CC() class SetAllContactReceipts(val enable: Boolean): CC() @@ -2962,7 +2962,7 @@ sealed class CC { is Console -> cmd is ShowActiveUser -> "/u" is CreateActiveUser -> { - val user = NewUser(profile, sameServers = sameServers, pastTimestamp = pastTimestamp) + val user = NewUser(profile, pastTimestamp = pastTimestamp) "/_create user ${json.encodeToString(user)}" } is ListUsers -> "/users" @@ -3293,7 +3293,6 @@ fun onOff(b: Boolean): String = if (b) "on" else "off" @Serializable data class NewUser( val profile: Profile?, - val sameServers: Boolean, val pastTimestamp: Boolean ) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index ff34640c6e..a413bf9896 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -593,6 +593,7 @@ test-suite simplex-chat-test MessageBatching MobileTests ProtocolTests + RandomServers RemoteTests SchemaDump ValidNames diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 40fafec504..28c141d28a 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -148,8 +148,10 @@ defaultChatConfig = defaultServers = DefaultAgentServers { smp = _defaultSMPServers, + useSMP = 4, ntf = _defaultNtfServers, xftp = L.map (presetServerCfg True) defaultXFTPServers, + useXFTP = L.length defaultXFTPServers, netCfg = defaultNetworkConfig }, tbqSize = 1024, @@ -178,7 +180,13 @@ _defaultSMPServers = L.fromList $ map (presetServerCfg True) - [ "smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion", + [ "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion", + "smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im,jssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion", + "smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im,rb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion", + "smp://1OwYGt-yqOfe2IyVHhxz3ohqo3aCCMjtB-8wn4X_aoY=@smp11.simplex.im,6ioorbm6i3yxmuoezrhjk6f6qgkc4syabh7m3so74xunb5nzr4pwgfqd.onion", + "smp://UkMFNAXLXeAAe0beCa4w6X_zp18PwxSaSjY17BKUGXQ=@smp12.simplex.im,ie42b5weq7zdkghocs3mgxdjeuycheeqqmksntj57rmejagmg4eor5yd.onion", + "smp://enEkec4hlR3UtKx2NMpOUK_K4ZuDxjWBO1d9Y4YXVaA=@smp14.simplex.im,aspkyu2sopsnizbyfabtsicikr2s4r3ti35jogbcekhm3fsoeyjvgrid.onion", + "smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion", "smp://hejn2gVIqNU6xjtGM3OwQeuk8ZEbDXVJXAlnSBJBWUA=@smp16.simplex.im,p3ktngodzi6qrf7w64mmde3syuzrv57y55hxabqcq3l5p6oi7yzze6qd.onion", "smp://ZKe4uxF4Z_aLJJOEsC-Y6hSkXgQS5-oc442JQGkyP8M=@smp17.simplex.im,ogtwfxyi3h2h5weftjjpjmxclhb5ugufa5rcyrmg7j4xlch7qsr5nuqd.onion", "smp://PtsqghzQKU83kYTlQ1VKg996dW4Cw4x_bvpKmiv8uns=@smp18.simplex.im,lyqpnwbs2zqfr45jqkncwpywpbtq7jrhxnib5qddtr6npjyezuwd3nqd.onion", @@ -188,13 +196,7 @@ _defaultSMPServers = (presetServerCfg False) [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", - "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion", - "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion", - "smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im,jssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion", - "smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im,rb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion", - "smp://1OwYGt-yqOfe2IyVHhxz3ohqo3aCCMjtB-8wn4X_aoY=@smp11.simplex.im,6ioorbm6i3yxmuoezrhjk6f6qgkc4syabh7m3so74xunb5nzr4pwgfqd.onion", - "smp://UkMFNAXLXeAAe0beCa4w6X_zp18PwxSaSjY17BKUGXQ=@smp12.simplex.im,ie42b5weq7zdkghocs3mgxdjeuycheeqqmksntj57rmejagmg4eor5yd.onion", - "smp://enEkec4hlR3UtKx2NMpOUK_K4ZuDxjWBO1d9Y4YXVaA=@smp14.simplex.im,aspkyu2sopsnizbyfabtsicikr2s4r3ti35jogbcekhm3fsoeyjvgrid.onion" + "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" ] _defaultNtfServers :: [NtfServer] @@ -380,11 +382,31 @@ withFileLock name = withEntityLock name . CLFile useServers :: UserProtocol p => ChatConfig -> SProtocolType p -> [ServerCfg p] -> NonEmpty (ServerCfg p) useServers ChatConfig {defaultServers} p = fromMaybe (cfgServers p defaultServers) . nonEmpty -cfgServers :: UserProtocol p => SProtocolType p -> (DefaultAgentServers -> NonEmpty (ServerCfg p)) +randomServers :: forall p. UserProtocol p => SProtocolType p -> ChatConfig -> IO (NonEmpty (ServerCfg p), [ServerCfg p]) +randomServers p ChatConfig {defaultServers} = do + let srvs = cfgServers p defaultServers + (enbldSrvs, dsbldSrvs) = L.partition (\ServerCfg {enabled} -> enabled) srvs + toUse = cfgServersToUse p defaultServers + if length enbldSrvs <= toUse + then pure (srvs, []) + else do + (enbldSrvs', srvsToDisable) <- splitAt toUse <$> shuffle enbldSrvs + let dsbldSrvs' = map (\srv -> (srv :: ServerCfg p) {enabled = False}) srvsToDisable + srvs' = sortOn server' $ enbldSrvs' <> dsbldSrvs' <> dsbldSrvs + pure (fromMaybe srvs $ L.nonEmpty srvs', srvs') + where + server' ServerCfg {server = ProtoServerWithAuth srv _} = srv + +cfgServers :: UserProtocol p => SProtocolType p -> DefaultAgentServers -> NonEmpty (ServerCfg p) cfgServers p DefaultAgentServers {smp, xftp} = case p of SPSMP -> smp SPXFTP -> xftp +cfgServersToUse :: UserProtocol p => SProtocolType p -> DefaultAgentServers -> Int +cfgServersToUse p DefaultAgentServers {useSMP, useXFTP} = case p of + SPSMP -> useSMP + SPXFTP -> useXFTP + -- enableSndFiles has no effect when mainApp is True startChatController :: Bool -> Bool -> CM' (Async ()) startChatController mainApp enableSndFiles = do @@ -523,7 +545,7 @@ processChatCommand cmd = processChatCommand' :: VersionRangeChat -> ChatCommand -> CM ChatResponse processChatCommand' vr = \case ShowActiveUser -> withUser' $ pure . CRActiveUser - CreateActiveUser NewUser {profile, sameServers, pastTimestamp} -> do + CreateActiveUser NewUser {profile, pastTimestamp} -> do forM_ profile $ \Profile {displayName} -> checkValidName displayName p@Profile {displayName} <- liftIO $ maybe generateRandomProfile pure profile u <- asks currentUser @@ -549,12 +571,10 @@ processChatCommand' vr = \case createContact db user simplexStatusContactProfile createContact db user simplexTeamContactProfile chooseServers :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> CM (NonEmpty (ServerCfg p), [ServerCfg p]) - chooseServers protocol - | sameServers = - asks currentUser >>= readTVarIO >>= \case - Nothing -> throwChatError CENoActiveUser - Just user -> chosenServers =<< withFastStore' (`getProtocolServers` user) - | otherwise = chosenServers [] + chooseServers protocol = + asks currentUser >>= readTVarIO >>= \case + Nothing -> asks config >>= liftIO . randomServers protocol + Just user -> chosenServers =<< withFastStore' (`getProtocolServers` user) where chosenServers servers = do cfg <- asks config @@ -7914,10 +7934,9 @@ chatCommandP = onOffP = ("on" $> True) <|> ("off" $> False) profileNames = (,) <$> displayName <*> fullNameP newUserP = do - sameServers <- "same_servers=" *> onOffP <* A.space <|> pure False (cName, fullName) <- profileNames let profile = Just Profile {displayName = cName, fullName, image = Nothing, contactLink = Nothing, preferences = Nothing} - pure NewUser {profile, sameServers, pastTimestamp = False} + pure NewUser {profile, pastTimestamp = False} jsonP :: J.FromJSON a => Parser a jsonP = J.eitherDecodeStrict' <$?> A.takeByteString groupProfile = do diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 18b6c694e4..301ef690a6 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -174,8 +174,10 @@ defaultChatHooks = data DefaultAgentServers = DefaultAgentServers { smp :: NonEmpty (ServerCfg 'PSMP), + useSMP :: Int, ntf :: [NtfServer], xftp :: NonEmpty (ServerCfg 'PXFTP), + useXFTP :: Int, netCfg :: NetworkConfig } diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index 07fac82677..a07968654d 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -105,7 +105,7 @@ createActiveUser cc = do loop = do displayName <- T.pack <$> getWithPrompt "display name" let profile = Just Profile {displayName, fullName = "", image = Nothing, contactLink = Nothing, preferences = Nothing} - execChatCommand' (CreateActiveUser NewUser {profile, sameServers = False, pastTimestamp = False}) `runReaderT` cc >>= \case + execChatCommand' (CreateActiveUser NewUser {profile, pastTimestamp = False}) `runReaderT` cc >>= \case CRActiveUser user -> pure user r -> do ts <- getCurrentTime diff --git a/src/Simplex/Chat/Terminal.hs b/src/Simplex/Chat/Terminal.hs index eb11bb0205..5cc695db04 100644 --- a/src/Simplex/Chat/Terminal.hs +++ b/src/Simplex/Chat/Terminal.hs @@ -39,8 +39,10 @@ terminalChatConfig = "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" ], + useSMP = 3, ntf = ["ntf://FB-Uop7RTaZZEG0ZLD2CIaTjsPh-Fw0zFAnb7QyA8Ks=@ntf2.simplex.im,ntg7jdjy2i3qbib3sykiho3enekwiaqg3icctliqhtqcg6jmoh6cxiad.onion"], xftp = L.map (presetServerCfg True) defaultXFTPServers, + useXFTP = L.length defaultXFTPServers, netCfg = defaultNetworkConfig { smpProxyMode = SPMUnknown, diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 93820365b0..fe40fcfcee 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -124,7 +124,6 @@ data User = User data NewUser = NewUser { profile :: Maybe Profile, - sameServers :: Bool, pastTimestamp :: Bool } deriving (Show) diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 9f33da78dc..26bc7a54de 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -97,7 +97,6 @@ chatDirectTests = do it "create second user" testCreateSecondUser it "multiple users subscribe and receive messages after restart" testUsersSubscribeAfterRestart it "both users have contact link" testMultipleUserAddresses - it "create user with default servers" testCreateUserDefaultServers it "create user with same servers" testCreateUserSameServers it "delete user" testDeleteUser it "users have different chat item TTL configuration, chat items expire" testUsersDifferentCIExpirationTTL @@ -1488,39 +1487,6 @@ testMultipleUserAddresses = showActiveUser alice "alice (Alice)" alice @@@ [("@bob", "hey alice")] -testCreateUserDefaultServers :: HasCallStack => FilePath -> IO () -testCreateUserDefaultServers = - testChat2 aliceProfile bobProfile $ - \alice _ -> do - alice #$> ("/smp smp://2345-w==@smp2.example.im smp://3456-w==@smp3.example.im:5224", id, "ok") - alice #$> ("/xftp xftp://2345-w==@xftp2.example.im xftp://3456-w==@xftp3.example.im:5224", id, "ok") - checkCustomServers alice - - alice ##> "/create user alisa" - showActiveUser alice "alisa" - - alice #$> ("/smp", id, "smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001") - alice #$> ("/xftp", id, "xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002") - - -- with same_servers=off - alice ##> "/user alice" - showActiveUser alice "alice (Alice)" - checkCustomServers alice - - alice ##> "/create user same_servers=off alisa2" - showActiveUser alice "alisa2" - - alice #$> ("/smp", id, "smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001") - alice #$> ("/xftp", id, "xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002") - where - checkCustomServers alice = do - alice ##> "/smp" - alice <## "smp://2345-w==@smp2.example.im" - alice <## "smp://3456-w==@smp3.example.im:5224" - alice ##> "/xftp" - alice <## "xftp://2345-w==@xftp2.example.im" - alice <## "xftp://3456-w==@xftp3.example.im:5224" - testCreateUserSameServers :: HasCallStack => FilePath -> IO () testCreateUserSameServers = testChat2 aliceProfile bobProfile $ @@ -1529,7 +1495,7 @@ testCreateUserSameServers = alice #$> ("/xftp xftp://2345-w==@xftp2.example.im xftp://3456-w==@xftp3.example.im:5224", id, "ok") checkCustomServers alice - alice ##> "/create user same_servers=on alisa" + alice ##> "/create user alisa" showActiveUser alice "alisa" checkCustomServers alice diff --git a/tests/RandomServers.hs b/tests/RandomServers.hs new file mode 100644 index 0000000000..0c6baa71bb --- /dev/null +++ b/tests/RandomServers.hs @@ -0,0 +1,51 @@ +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +module RandomServers where + +import Control.Monad (replicateM) +import qualified Data.List.NonEmpty as L +import Simplex.Chat (cfgServers, cfgServersToUse, defaultChatConfig, randomServers) +import Simplex.Chat.Controller (ChatConfig (..)) +import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..)) +import Simplex.Messaging.Protocol (ProtoServerWithAuth (..), SProtocolType (..), UserProtocol) +import Test.Hspec + +randomServersTests :: Spec +randomServersTests = describe "choosig random servers" $ do + it "should choose 4 random SMP servers and keep the rest disabled" testRandomSMPServers + it "should keep all 6 XFTP servers" testRandomXFTPServers + +deriving instance Eq (ServerCfg p) + +testRandomSMPServers :: IO () +testRandomSMPServers = do + [srvs1, srvs2, srvs3] <- + replicateM 3 $ + checkEnabled SPSMP 4 False =<< randomServers SPSMP defaultChatConfig + (srvs1 == srvs2 && srvs2 == srvs3) `shouldBe` False -- && to avoid rare failures + +testRandomXFTPServers :: IO () +testRandomXFTPServers = do + [srvs1, srvs2, srvs3] <- + replicateM 3 $ + checkEnabled SPXFTP 6 True =<< randomServers SPXFTP defaultChatConfig + (srvs1 == srvs2 && srvs2 == srvs3) `shouldBe` True + +checkEnabled :: UserProtocol p => SProtocolType p -> Int -> Bool -> (L.NonEmpty (ServerCfg p), [ServerCfg p]) -> IO [ServerCfg p] +checkEnabled p n allUsed (srvs, _) = do + let def = defaultServers defaultChatConfig + cfgSrvs = L.sortWith server' $ cfgServers p def + toUse = cfgServersToUse p def + srvs == cfgSrvs `shouldBe` allUsed + L.map enable srvs `shouldBe` L.map enable cfgSrvs + let enbldSrvs = L.filter (\ServerCfg {enabled} -> enabled) srvs + toUse `shouldBe` n + length enbldSrvs `shouldBe` n + pure enbldSrvs + where + server' ServerCfg {server = ProtoServerWithAuth srv _} = srv + enable :: forall p. ServerCfg p -> ServerCfg p + enable srv = (srv :: ServerCfg p) {enabled = False} diff --git a/tests/Test.hs b/tests/Test.hs index cbe8b627ec..3d59b840dd 100644 --- a/tests/Test.hs +++ b/tests/Test.hs @@ -10,6 +10,7 @@ import MarkdownTests import MessageBatching import MobileTests import ProtocolTests +import RandomServers import RemoteTests import SchemaDump import Test.Hspec hiding (it) @@ -30,6 +31,7 @@ main = do around tmpBracket $ describe "WebRTC encryption" webRTCTests describe "Valid names" validNameTests describe "Message batching" batchingTests + describe "Random servers" randomServersTests around testBracket $ do describe "Mobile API Tests" mobileTests describe "SimpleX chat client" chatTests