From bd4bd47e008624ac4eee83d46ba3731fa7865199 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 02:53:34 +0100 Subject: [PATCH] Recover join dialog after clipboard and connection errors --- .../utun/chat/ui/screens/JoinChannelDialog.kt | 69 +++++++++++++------ .../app/src/main/res/values-ru/strings.xml | 2 + .../app/src/main/res/values/strings.xml | 2 + 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/JoinChannelDialog.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/JoinChannelDialog.kt index 00e20cab..6f1e07df 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/JoinChannelDialog.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/JoinChannelDialog.kt @@ -1,7 +1,6 @@ package com.utun.chat.ui.screens import com.utun.chat.R -import com.utun.chat.data.AppLanguage import androidx.compose.ui.res.stringResource import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -10,42 +9,65 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ContentPaste import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.unit.dp import com.utun.chat.data.InviteData import com.utun.chat.data.InviteLink import com.utun.chat.data.LogManager +import kotlinx.coroutines.CancellationException +/** Автоматически передаёт валидную ссылку из ввода/QR/буфера в onConnect; сетевой результат показывает вызывающий экран. + * Callback должен принять запрос и закрыть диалог. Ошибка запуска разрешает повторить ту же ссылку. Секреты ссылки не логируются. */ @Composable fun JoinChannelDialog(onConnect: (InviteData) -> Unit, onDismiss: () -> Unit) { val clipboard = LocalClipboardManager.current - var link by remember { mutableStateOf("") } - var error by remember { mutableStateOf(null) } + var link by rememberSaveable { mutableStateOf("") } + var error by remember { mutableStateOf(null) } var connecting by remember { mutableStateOf(false) } + var retry by remember { mutableIntStateOf(0) } val connect by rememberUpdatedState(onConnect) - LaunchedEffect(Unit) { - val text = clipboard.getText()?.text?.trim().orEmpty() - if (text.startsWith("utun://")) { - LogManager.addLog("INFO", "INVITE", "Invite link pasted from clipboard") - link = text + fun paste(onlyInvite: Boolean) { + try { + val text = clipboard.getText()?.text?.trim().orEmpty() + if (!onlyInvite || text.startsWith("utun://")) { + LogManager.addLog("INFO", "INVITE", "Invite input read from clipboard") + link = text + } + } catch (e: Exception) { + LogManager.addLog("ERROR", "INVITE", "Clipboard read failed: $e") + error = R.string.invite_paste_failed } } - LaunchedEffect(link) { - error = null + LaunchedEffect(Unit) { + if (link.isEmpty()) paste(onlyInvite = true) + } + + LaunchedEffect(link, retry) { val text = link.trim() if (text.isEmpty() || connecting) return@LaunchedEffect - val invite = InviteLink.decode(text) - if (invite == null) { - LogManager.addLog("WARN", "INVITE", "Invalid invite link in join dialog") - error = AppLanguage.text(R.string.invalid_invite_link) - } else { - connecting = true - LogManager.addLog("INFO", "INVITE", "connect: ch=0x%016x node=0x%016x addrs=%d" - .format(invite.channelId, invite.nodeId, invite.addrs.size)) - connect(invite) + error = null + try { + val invite = InviteLink.decode(text) + if (invite == null) { + LogManager.addLog("WARN", "INVITE", "Invalid invite link in join dialog") + error = R.string.invalid_invite_link + } else { + connecting = true + LogManager.addLog("INFO", "INVITE", "connect: ch=0x%016x node=0x%016x addrs=%d" + .format(invite.channelId, invite.nodeId, invite.addrs.size)) + connect(invite) + } + } catch (e: CancellationException) { + connecting = false + throw e + } catch (e: Exception) { + LogManager.addLog("ERROR", "INVITE", "Invite connection request failed: ${e.javaClass.simpleName}") + connecting = false + error = R.string.invite_connect_failed } } @@ -65,11 +87,11 @@ fun JoinChannelDialog(onConnect: (InviteData) -> Unit, onDismiss: () -> Unit) { } OutlinedTextField( value = link, - onValueChange = { link = it }, + onValueChange = { error = null; link = it }, label = { Text(stringResource(R.string.invite_link)) }, placeholder = { Text("utun://...") }, trailingIcon = { - IconButton(onClick = { link = clipboard.getText()?.text.orEmpty() }, enabled = !connecting) { + IconButton(onClick = { paste(onlyInvite = false) }, enabled = !connecting) { Icon(Icons.Filled.ContentPaste, stringResource(R.string.paste_invite_link)) } }, @@ -78,7 +100,10 @@ fun JoinChannelDialog(onConnect: (InviteData) -> Unit, onDismiss: () -> Unit) { isError = error != null, modifier = Modifier.fillMaxWidth() ) - error?.let { Text(it, color = MaterialTheme.colorScheme.error) } + error?.let { Text(stringResource(it), color = MaterialTheme.colorScheme.error) } + if (error == R.string.invite_connect_failed) { + TextButton(onClick = { retry++ }) { Text(stringResource(R.string.retry)) } + } if (connecting) Text(stringResource(R.string.connecting_peer), color = MaterialTheme.colorScheme.primary) } }, diff --git a/tools/chatgui-android/app/src/main/res/values-ru/strings.xml b/tools/chatgui-android/app/src/main/res/values-ru/strings.xml index b022427f..c2afccd3 100644 --- a/tools/chatgui-android/app/src/main/res/values-ru/strings.xml +++ b/tools/chatgui-android/app/src/main/res/values-ru/strings.xml @@ -161,6 +161,8 @@ "QR-код" "Не удалось собрать ссылку" Не удалось скопировать ссылку-приглашение + Не удалось прочитать буфер обмена. Введите ссылку вручную. + Не удалось начать подключение. Повторите попытку. %1$s: %2$s "Скопировано!" "Копировать ссылку" diff --git a/tools/chatgui-android/app/src/main/res/values/strings.xml b/tools/chatgui-android/app/src/main/res/values/strings.xml index cff1ebce..c3428e1f 100644 --- a/tools/chatgui-android/app/src/main/res/values/strings.xml +++ b/tools/chatgui-android/app/src/main/res/values/strings.xml @@ -162,6 +162,8 @@ "QR code" "Could not build invite link" Could not copy the invite link + Could not read the clipboard. Enter an invite link manually. + Could not start connecting. Please try again. %1$s: %2$s "Copied!" "Copy link"