diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt index 65098cea2..a6f0d2c9b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt @@ -19,8 +19,8 @@ import dev.icerock.moko.resources.compose.painterResource import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.* -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.* import chat.simplex.common.views.database.PassphraseStrength import chat.simplex.common.views.database.validKey import chat.simplex.res.MR @@ -123,6 +123,7 @@ fun DefaultConfigurableTextField( isValid: (String) -> Boolean, keyboardActions: KeyboardActions = KeyboardActions(), keyboardType: KeyboardType = KeyboardType.Text, + fontSize: TextUnit = 16.sp, dependsOn: State? = null, ) { var valid by remember { mutableStateOf(isValid(state.value.text)) } @@ -175,14 +176,14 @@ fun DefaultConfigurableTextField( textStyle = TextStyle.Default.copy( color = color, fontWeight = FontWeight.Normal, - fontSize = 16.sp + fontSize = fontSize ), interactionSource = interactionSource, decorationBox = @Composable { innerTextField -> TextFieldDefaults.TextFieldDecorationBox( value = state.value.text, innerTextField = innerTextField, - placeholder = { Text(placeholder, color = MaterialTheme.colors.secondary) }, + placeholder = { Text(placeholder, color = MaterialTheme.colors.secondary, fontSize = fontSize, maxLines = 1, overflow = TextOverflow.Ellipsis) }, singleLine = true, enabled = enabled, isError = !valid, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt index 904b2fc34..290bc2cd0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt @@ -20,6 +20,7 @@ fun ExposedDropDownSetting( values: List>, selection: State, textColor: Color = MaterialTheme.colors.secondary, + fontSize: TextUnit = 16.sp, label: String? = null, enabled: State = mutableStateOf(true), minWidth: Dp = 200.dp, @@ -43,7 +44,8 @@ fun ExposedDropDownSetting( Modifier.widthIn(max = maxWidth), maxLines = 1, overflow = TextOverflow.Ellipsis, - color = textColor + color = textColor, + fontSize = fontSize, ) Spacer(Modifier.size(12.dp)) Icon( @@ -69,6 +71,7 @@ fun ExposedDropDownSetting( maxLines = 1, overflow = TextOverflow.Ellipsis, color = if (isInDarkTheme()) MenuTextColorDark else Color.Black, + fontSize = fontSize, ) } } @@ -91,6 +94,6 @@ fun ExposedDropDownSettingRow( onSelected: (T) -> Unit ) { SettingsActionItemWithContent(icon, title, iconColor = iconTint, disabled = !enabled.value) { - ExposedDropDownSetting(values, selection ,textColor, label, enabled, minWidth, maxWidth, onSelected) + ExposedDropDownSetting(values, selection ,textColor, label = label, enabled = enabled, minWidth = minWidth, maxWidth = maxWidth, onSelected = onSelected) } } 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 c1c5d978c..1ac958f93 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 @@ -174,8 +174,6 @@ private fun ConnectMobileViewLayout( sessionCode: String?, port: String?, staleQrCode: Boolean = false, - editEnabled: Boolean = false, - editClicked: () -> Unit = {}, refreshQrCode: () -> Unit = {}, UnderQrLayout: @Composable () -> Unit = {}, ) { @@ -201,16 +199,7 @@ private fun ConnectMobileViewLayout( } } SectionTextFooter(annotatedStringResource(MR.strings.open_on_mobile_and_scan_qr_code), textAlign = TextAlign.Center) - Row(verticalAlignment = Alignment.CenterVertically) { - SectionTextFooter(annotatedStringResource(MR.strings.waiting_for_mobile_to_connect_on_port, port), textAlign = TextAlign.Center) - if (editEnabled) { - Spacer(Modifier.width(4.dp)) - IconButton(editClicked, Modifier.size(16.dp)) { - Icon(painterResource(MR.images.ic_edit), stringResource(MR.strings.edit_verb), Modifier.size(16.dp), tint = MaterialTheme.colors.primary) - } - Spacer(Modifier.width(DEFAULT_PADDING)) - } - } + SectionTextFooter(annotatedStringResource(MR.strings.waiting_for_mobile_to_connect), textAlign = TextAlign.Center) UnderQrLayout() @@ -249,7 +238,9 @@ private fun ConnectMobileViewLayout( } } } - SectionBottomSpacer() + if (invitation != null) { + SectionBottomSpacer() + } } } @@ -275,10 +266,9 @@ private fun showAddingMobileDevice(connecting: MutableState) { @Composable fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState, connecting: MutableState, close: () -> Unit) { - val cachedR = remember { mutableStateOf(null) } + var cachedR by remember { mutableStateOf(null) } val customAddress = rememberSaveable { mutableStateOf(null) } val customPort = rememberSaveable { mutableStateOf(null) } - var editing by rememberSaveable { mutableStateOf(false) } val startRemoteHost = suspend { val r = chatModel.controller.startRemoteHost( rhId = null, @@ -287,7 +277,7 @@ fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState, c port = if (customPort.value != cachedR.port) customPort.value else cachedR.rh?.bindPort_ ) if (r != null) { - cachedR.value = r + cachedR = r connecting.value = true customAddress.value = cachedR.address customPort.value = cachedR.port @@ -307,23 +297,20 @@ fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState, c val remoteDeviceName = pairing.value?.first?.hostDeviceName ConnectMobileViewLayout( title = if (!showTitle) null else if (cachedSessionCode == null) stringResource(MR.strings.link_a_mobile) else stringResource(MR.strings.verify_connection), - invitation = cachedR.invitation, + invitation = cachedR?.invitation, deviceName = remoteDeviceName, sessionCode = cachedSessionCode, - port = cachedR.value?.ctrlPort, - staleQrCode = staleQrCode.value || (cachedR.address != customAddress.value && customAddress.value != null) || (cachedR.port != customPort.value && customPort.value != null), - editEnabled = !editing && cachedR.addresses.isNotEmpty(), - editClicked = { editing = true }, + port = cachedR?.ctrlPort, + staleQrCode = staleQrCode.value || (cachedR.address != customAddress.value && customAddress.value != null) || cachedR.port != customPort.value, refreshQrCode = { withBGApi { if (chatController.stopRemoteHost(null)) { startRemoteHost() staleQrCode.value = false - editing = false } } }, - UnderQrLayout = { UnderQrLayout(editing, cachedR, customAddress, customPort) } + UnderQrLayout = { UnderQrLayout(cachedR, customAddress, customPort) } ) val oldRemoteHostId by remember { mutableStateOf(chatModel.currentRemoteHost.value?.remoteHostId) } LaunchedEffect(remember { chatModel.currentRemoteHost }.value) { @@ -353,10 +340,9 @@ fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState, c private fun showConnectMobileDevice(rh: RemoteHostInfo, connecting: MutableState) { ModalManager.start.showModalCloseable { close -> - val cachedR = remember { mutableStateOf(null) } + var cachedR by remember { mutableStateOf(null) } val customAddress = rememberSaveable { mutableStateOf(null) } val customPort = rememberSaveable { mutableStateOf(null) } - var editing by rememberSaveable { mutableStateOf(false) } val startRemoteHost = suspend { val r = chatModel.controller.startRemoteHost( rhId = rh.remoteHostId, @@ -365,7 +351,7 @@ private fun showConnectMobileDevice(rh: RemoteHostInfo, connecting: MutableState port = if (customPort.value != cachedR.port) customPort.value else cachedR.rh?.bindPort_ ?: rh.bindPort_ ) if (r != null) { - cachedR.value = r + cachedR = r connecting.value = true customAddress.value = cachedR.address customPort.value = cachedR.port @@ -384,22 +370,19 @@ private fun showConnectMobileDevice(rh: RemoteHostInfo, connecting: MutableState } ConnectMobileViewLayout( title = if (cachedSessionCode == null) stringResource(MR.strings.scan_from_mobile) else stringResource(MR.strings.verify_connection), - invitation = cachedR.invitation, + invitation = cachedR?.invitation, deviceName = pairing.value?.first?.hostDeviceName ?: rh.hostDeviceName, sessionCode = cachedSessionCode, - port = cachedR.value?.ctrlPort, - staleQrCode = (cachedR.address != customAddress.value && customAddress.value != null) || (cachedR.port != customPort.value && customPort.value != null), - editEnabled = !editing && cachedR.addresses.isNotEmpty(), - editClicked = { editing = true }, + port = cachedR?.ctrlPort, + staleQrCode = (cachedR.address != customAddress.value && customAddress.value != null) || cachedR.port != customPort.value, refreshQrCode = { withBGApi { if (chatController.stopRemoteHost(rh.remoteHostId)) { startRemoteHost() - editing = false } } }, - UnderQrLayout = { UnderQrLayout(editing, cachedR, customAddress, customPort) } + UnderQrLayout = { UnderQrLayout(cachedR, customAddress, customPort) } ) LaunchedEffect(remember { chatModel.currentRemoteHost }.value) { if (cachedR.remoteHostId != null && chatModel.currentRemoteHost.value?.remoteHostId == cachedR.remoteHostId) { @@ -453,48 +436,65 @@ private fun showConnectedMobileDevice(rh: RemoteHostInfo, disconnectHost: () -> } @Composable -private fun UnderQrLayout(editing: Boolean, cachedR: State, customAddress: MutableState, customPort: MutableState) { - if (editing) { - Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) { - ExposedDropDownSetting( - cachedR.addresses.map { it to it.address + " (${it.`interface`})" }, - customAddress, - textColor = MaterialTheme.colors.onBackground, - minWidth = 250.dp, - maxWidth = with(LocalDensity.current) { 250.sp.toDp() }, - onSelected = { - customAddress.value = it - } - ) - val portUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue((customPort.value ?: cachedR.port!!).toString())) +private fun UnderQrLayout(cachedR: CR.RemoteHostStarted?, customAddress: MutableState, customPort: MutableState) { + Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) { + ExposedDropDownSetting( + cachedR.addresses.map { it to it.address + " (${it.`interface`})" }, + customAddress, + textColor = MaterialTheme.colors.onBackground, + fontSize = 14.sp, + minWidth = 250.dp, + maxWidth = with(LocalDensity.current) { 250.sp.toDp() }, + enabled = remember { mutableStateOf(cachedR.addresses.size > 1) }, + onSelected = { + customAddress.value = it } - Spacer(Modifier.width(DEFAULT_PADDING)) + ) + val portUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf(TextFieldValue((customPort.value ?: cachedR.port!!).toString())) + } + Spacer(Modifier.width(DEFAULT_PADDING)) + Box { DefaultConfigurableTextField( portUnsaved, - stringResource(MR.strings.port_verb), - modifier = Modifier.widthIn(max = 100.dp), - isValid = { validPort(it) && it.toInt() > 1023 }, + stringResource(MR.strings.random_port), + modifier = Modifier.widthIn(max = 132.dp), + isValid = { (validPort(it) && it.toInt() > 1023) || it.isBlank() }, keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done) }), keyboardType = KeyboardType.Number, + fontSize = 14.sp, ) - LaunchedEffect(Unit) { - snapshotFlow { portUnsaved.value.text } - .distinctUntilChanged() - .collect { - if (validPort(it) && it.toInt() > 1023) { - customPort.value = it.toInt() - } - } + if (validPort(portUnsaved.value.text) && portUnsaved.value.text.toInt() > 1023) { + Icon(painterResource(MR.images.ic_edit), stringResource(MR.strings.edit_verb), Modifier.padding(end = 56.dp).size(16.dp).align(Alignment.CenterEnd), tint = MaterialTheme.colors.secondary) + IconButton(::showOpenPortAlert, Modifier.align(Alignment.TopEnd).padding(top = 2.dp)) { + Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.primary) + } } } + LaunchedEffect(Unit) { + snapshotFlow { portUnsaved.value.text } + .distinctUntilChanged() + .collect { + if (validPort(it) && it.toInt() > 1023) { + customPort.value = it.toInt() + } else { + customPort.value = null + } + } + } } } -private val State.rh: RemoteHostInfo? get() = value?.remoteHost_ -private val State.remoteHostId: Long? get() = value?.remoteHost_?.remoteHostId -private val State.invitation: String? get() = value?.invitation -private val State.address: RemoteCtrlAddress? get() = value?.localAddrs?.firstOrNull() -private val State.addresses: List get() = - (if (controller.appPrefs.developerTools.get()) value?.localAddrs else value?.localAddrs?.filterNot { it.address == "127.0.0.1" }) ?: emptyList() -private val State.port: Int? get() = value?.ctrlPort?.toIntOrNull() +private fun showOpenPortAlert() { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.open_port_in_firewall_title), + text = generalGetString(MR.strings.open_port_in_firewall_desc), + ) +} + +private val CR.RemoteHostStarted?.rh: RemoteHostInfo? get() = this?.remoteHost_ +private val CR.RemoteHostStarted?.remoteHostId: Long? get() = this?.remoteHost_?.remoteHostId +private val CR.RemoteHostStarted?.address: RemoteCtrlAddress? get() = this?.localAddrs?.firstOrNull() +private val CR.RemoteHostStarted?.addresses: List get() = + (if (controller.appPrefs.developerTools.get()) this?.localAddrs else this?.localAddrs?.filterNot { it.address == "127.0.0.1" }) ?: emptyList() +private val CR.RemoteHostStarted?.port: Int? get() = this?.ctrlPort?.toIntOrNull() 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 bfd61aa9e..386f43933 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1665,7 +1665,7 @@ Disconnect desktop? Only one device can work at the same time Use from desktop in mobile app and scan QR code.]]> - %s]]> + Waiting for mobile to connect: Bad desktop address Incompatible version Desktop app version %s is not compatible with this app. @@ -1693,6 +1693,9 @@ Not compatible! Refresh No connected mobile + Random + Open port in firewall + To allow a mobile app to connect to the desktop, open this port in your firewall if you have it enabled Coming soon! 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 13b5cb239..1f8f75c12 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -1581,7 +1581,7 @@ Schnellerer Gruppenbeitritt und zuverlässigere Nachrichtenzustellung. Verknüpfte Mobiltelefone Dieser Gerätename - %s warten]]> + Auf die Mobiltelefonverbindung warten: Laden der Datei Zu einem Mobiltelefon verbinden Vom Desktop aus nutzen 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 a9d315f68..efe9b1003 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -1486,7 +1486,7 @@ Bureau Connecté au bureau Ce nom d\'appareil - %s]]> + En attente d\'une connexion mobile: Chargement du fichier Connexion au bureau Appareils de bureau 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 0883adfdb..9ed305da7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -1517,6 +1517,6 @@ - avvisa facoltativamente i contatti eliminati. \n- nomi del profilo con spazi. \n- e molto altro! - %s]]> + In attesa che il cellulare si connette: autore \ No newline at end of file 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 0a258fdcb..6cef9571d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -1516,7 +1516,7 @@ \n- en meer! %s is verbroken]]> auteur - %s]]> + Wachten tot mobiel verbinding maakt: Automatisch verbinden Wachten op desktop… Desktop gevonden 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 a9549080a..8f4872478 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -1496,7 +1496,7 @@ Szybsze dołączenie i bardziej niezawodne wiadomości. Połączone telefony Nazwa tego urządzenia - %s]]> + Oczekiwanie na połączenie telefonu: Ładowanie pliku Znaleziono komputer Urządzenia komputerowe 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 dc5771316..f26f77c33 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -1601,7 +1601,7 @@ Проверить соединение Соединяться автоматически Ожидается подключение… - %s]]> + Ожидается подключение мобильного: Компьютер найден Несовместимая версия! автор 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 2f2c27e03..ed2b9986c 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 @@ -1517,7 +1517,7 @@ - 可选择通知已删除的联系人。 \n- 带空格的个人资料名称。 \n- 以及更多! - %s 进行连接]]> + 正等待移动设备 进行连接: 作者 自动连接 等待桌面中…