diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt index 5ae9e32341..9ec7d974f1 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt @@ -47,12 +47,13 @@ class AlertManager { onConfirm: (() -> Unit)? = null, dismissText: String = generalGetString(R.string.cancel_verb), onDismiss: (() -> Unit)? = null, + onDismissRequest: (() -> Unit)? = null, destructive: Boolean = false ) { val alertText: (@Composable () -> Unit)? = if (text == null) null else { -> Text(text) } showAlert { AlertDialog( - onDismissRequest = this::hideAlert, + onDismissRequest = { onDismissRequest?.invoke(); hideAlert() }, title = { Text(title) }, text = alertText, confirmButton = { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DataClasses.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DataClasses.kt new file mode 100644 index 0000000000..9fdf2b3966 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DataClasses.kt @@ -0,0 +1,12 @@ +package chat.simplex.app.views.helpers + +interface ValueTitle { + val value: T + val title: String +} + +data class ValueTitleDesc ( + override val value: T, + override val title: String, + val description: String +): ValueTitle diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Section.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Section.kt index 9ec912abcb..bc653fa2b3 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Section.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Section.kt @@ -1,13 +1,20 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.* -import androidx.compose.runtime.Composable +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Check +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.helpers.ValueTitleDesc +import chat.simplex.app.views.helpers.ValueTitle @Composable fun SectionView(title: String? = null, content: (@Composable () -> Unit)) { @@ -25,7 +32,33 @@ fun SectionView(title: String? = null, content: (@Composable () -> Unit)) { } @Composable -fun SectionItemView(click: (() -> Unit)? = null, height: Dp = 46.dp, disabled: Boolean = false, content: (@Composable () -> Unit)) { +fun SectionViewSelectable( + title: String?, + currentValue: MutableState, + values: List>, + onSelected: (T) -> Unit, +) { + SectionView(title) { + LazyColumn( + Modifier.padding(horizontal = 8.dp) + ) { + items(values.size) { index -> + val item = values[index] + SectionItemViewSpaceBetween({ onSelected(item.value) }, padding = PaddingValues()) { + Text(item.title) + if (currentValue.value == item.value) { + Icon(Icons.Outlined.Check, item.title, tint = HighOrLowlight) + } + } + Spacer(Modifier.padding(horizontal = 4.dp)) + } + } + } + SectionTextFooter(values.first { it.value == currentValue.value }.description) +} + +@Composable +fun SectionItemView(click: (() -> Unit)? = null, height: Dp = 46.dp, disabled: Boolean = false, content: (@Composable RowScope.() -> Unit)) { val modifier = Modifier .padding(horizontal = 8.dp) .fillMaxWidth() @@ -59,6 +92,45 @@ fun SectionItemViewSpaceBetween( } } +@Composable +fun SectionItemWithValue( + title: String, + currentValue: State, + values: List>, + label: String? = null, + icon: ImageVector? = null, + iconTint: Color = HighOrLowlight, + enabled: State = mutableStateOf(true), + onSelected: () -> Unit +) { + SectionItemView(click = if (enabled.value) onSelected else null) { + if (icon != null) { + Icon( + icon, + title, + Modifier.padding(end = 8.dp), + tint = iconTint + ) + } + Text(title, color = if (enabled.value) Color.Unspecified else HighOrLowlight) + + Spacer(Modifier.fillMaxWidth().weight(1f)) + + Row( + Modifier.padding(start = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End + ) { + Text( + values.first { it.value == currentValue.value }.title + (if (label != null) " $label" else ""), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = HighOrLowlight + ) + } + } +} + @Composable fun SectionTextFooter(text: String) { Text( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt index a6fd331681..f15e1cc29b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt @@ -2,7 +2,9 @@ package chat.simplex.app.views.usersettings import SectionDivider import SectionItemView +import SectionItemWithValue import SectionView +import SectionViewSelectable import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.material.icons.Icons @@ -10,10 +12,7 @@ import androidx.compose.material.icons.outlined.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.simplex.app.R @@ -71,9 +70,15 @@ fun NetworkAndServersView( } }, useOnion = { + if (onionHosts.value == it) return@NetworkAndServersLayout val prevValue = onionHosts.value onionHosts.value = it - updateNetworkSettingsDialog(onDismiss = { + val startsWith = when (it) { + OnionHosts.NEVER -> generalGetString(R.string.network_use_onion_hosts_no_desc_in_alert) + OnionHosts.PREFER -> generalGetString(R.string.network_use_onion_hosts_prefer_desc_in_alert) + OnionHosts.REQUIRED -> generalGetString(R.string.network_use_onion_hosts_required_desc_in_alert) + } + updateOnionHostsDialog(startsWith, onDismiss = { onionHosts.value = prevValue }) { withApi { @@ -117,9 +122,7 @@ fun NetworkAndServersView( UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy) } SectionDivider() - SectionItemView { - UseOnionHosts(onionHosts, networkUseSocksProxy, useOnion) - } + UseOnionHosts(onionHosts, networkUseSocksProxy, showSettingsModal, useOnion) if (developerTools) { SectionDivider() SettingsActionItem(Icons.Outlined.Cable, stringResource(R.string.network_settings), showSettingsModal { AdvancedNetworkSettingsView(it) }) @@ -161,112 +164,58 @@ fun UseSocksProxySwitch( } @Composable -private fun UseOnionHosts(onionHosts: MutableState, enabled: State, useOnion: (OnionHosts) -> Unit) { +private fun UseOnionHosts( + onionHosts: MutableState, + enabled: State, + showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + useOnion: (OnionHosts) -> Unit, +) { val values = remember { OnionHosts.values().map { when (it) { - OnionHosts.NEVER -> OnionHosts.NEVER to generalGetString(R.string.network_use_onion_hosts_no) - OnionHosts.PREFER -> OnionHosts.PREFER to generalGetString(R.string.network_use_onion_hosts_prefer) - OnionHosts.REQUIRED -> OnionHosts.REQUIRED to generalGetString(R.string.network_use_onion_hosts_required) + OnionHosts.NEVER -> ValueTitleDesc(OnionHosts.NEVER, generalGetString(R.string.network_use_onion_hosts_no), generalGetString(R.string.network_use_onion_hosts_no_desc)) + OnionHosts.PREFER -> ValueTitleDesc(OnionHosts.PREFER, generalGetString(R.string.network_use_onion_hosts_prefer), generalGetString(R.string.network_use_onion_hosts_prefer_desc)) + OnionHosts.REQUIRED -> ValueTitleDesc(OnionHosts.REQUIRED, generalGetString(R.string.network_use_onion_hosts_required), generalGetString(R.string.network_use_onion_hosts_required_desc)) } } } - ExposedDropDownSettingRow( + val onSelected = showSettingsModal { + Column( + Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.Start, + ) { + Text( + stringResource(R.string.network_use_onion_hosts), + Modifier.padding(start = 16.dp, end = 16.dp, bottom = 24.dp), + style = MaterialTheme.typography.h1 + ) + SectionViewSelectable(null, onionHosts, values, useOnion) + } + } + + SectionItemWithValue( generalGetString(R.string.network_use_onion_hosts), - values, onionHosts, + values, icon = Icons.Outlined.Security, enabled = enabled, - onSelected = useOnion + onSelected = onSelected ) } -@Composable -fun ExposedDropDownSettingRow( - title: String, - values: List>, - selection: State, - label: String? = null, - icon: ImageVector? = null, - iconTint: Color = HighOrLowlight, - enabled: State = mutableStateOf(true), - onSelected: (T) -> Unit +private fun updateOnionHostsDialog( + startsWith: String = "", + message: String = generalGetString(R.string.updating_settings_will_reconnect_client_to_all_servers), + onDismiss: () -> Unit, + onConfirm: () -> Unit ) { - Row( - Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - var expanded by remember { mutableStateOf(false) } - - if (icon != null) { - Icon( - icon, - "", - Modifier.padding(end = 8.dp), - tint = iconTint - ) - } - Text(title, color = if (enabled.value) Color.Unspecified else HighOrLowlight) - - Spacer(Modifier.fillMaxWidth().weight(1f)) - - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { - expanded = !expanded && enabled.value - } - ) { - Row( - Modifier.padding(start = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.End - ) { - Text( - values.first { it.first == selection.value }.second + (if (label != null) " $label" else ""), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = HighOrLowlight - ) - Spacer(Modifier.size(12.dp)) - Icon( - if (!expanded) Icons.Outlined.ExpandMore else Icons.Outlined.ExpandLess, - generalGetString(R.string.icon_descr_more_button), - tint = HighOrLowlight - ) - } - ExposedDropdownMenu( - modifier = Modifier.widthIn(min = 200.dp), - expanded = expanded, - onDismissRequest = { - expanded = false - } - ) { - values.forEach { selectionOption -> - DropdownMenuItem( - onClick = { - onSelected(selectionOption.first) - expanded = false - } - ) { - Text( - selectionOption.second + (if (label != null) " $label" else ""), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - } - } - } - } -} - -private fun updateNetworkSettingsDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) { AlertManager.shared.showAlertDialog( - title = generalGetString(R.string.update_network_settings_question), - text = generalGetString(R.string.updating_settings_will_reconnect_client_to_all_servers), + title = generalGetString(R.string.update_onion_hosts_settings_question), + text = startsWith + "\n\n" + message, confirmText = generalGetString(R.string.update_network_settings_confirmation), onDismiss = onDismiss, onConfirm = onConfirm, + onDismissRequest = onDismiss ) } diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml index 51b80d3f74..daa5ebc3ce 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -318,10 +318,17 @@ Соединяться с серверами через SOCKS прокси через порт 9050? Прокси должен быть запущен до включения этой опции. Использовать прямое соединение с Интернет? Если вы подтвердите, серверы смогут видеть ваш IP адрес, а провайдер - с какими серверами вы соединяетесь. + Обновить настройки .onion хостов? Использовать .onion хосты Когда возможно Нет Обязательно + Onion хосты используются, если возможно. + Onion хосты не используются. + Подключаться только к onion хостам. + Onion хосты используются, если возможно. + Onion хосты не используются. + Подключаться только к onion хостам. Интерфейс diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index ffa77d9582..032eb9fa6f 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -322,10 +322,17 @@ Access the servers via SOCKS proxy on port 9050? 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 Required + Onion hosts will be used when available. + Onion hosts will not be used. + Onion hosts will be required for connection. + Onion hosts will be used when available. + Onion hosts will not be used. + Onion hosts will be required for connection. Appearance