feat(mobile): redesign connect screen and instances sheet

- Connect screen leads with Scan QR code plus a plain-words hint of where the
  code lives; manual URL entry is collapsed behind Connect by address (expanded
  automatically on web where scanning is unavailable); saved connections show a
  per-row connecting spinner
- Instances sheet is list-first: the active instance shows a live status dot
  and transport (Connected - Local network / Private relay), rows connect on
  tap with an inline spinner, and the add/edit form hides behind Scan QR code /
  Add by address
- Deleting the last instance returns to the connect screen: without a runtime
  endpoint the native app no longer bootstraps against the webview's own origin
  (which faked a successful connection), and the connect screen renders
  regardless of a stale isConnected flag
This commit is contained in:
Bohdan Triapitsyn
2026-07-10 01:44:42 +03:00
parent 91a95bfdaa
commit e74834a739
11 changed files with 307 additions and 184 deletions
+5
View File
@@ -51,7 +51,9 @@ export const dict = {
'mobile.connect.cancelPassword': 'Use another server',
'mobile.connect.connecting': 'Connecting...',
'mobile.connect.scanQr': 'Scan QR code',
'mobile.connect.welcome.scanHint': 'On your computer, open «Add a device» to show a QR code, then scan it here.',
'mobile.connect.advanced': 'Advanced',
'mobile.connect.manual.toggle': 'Connect by address',
'mobile.connect.scan.permissionDenied': 'Camera access is off. Enable it in Settings to scan a QR code.',
'mobile.connect.scan.failed': 'Could not scan that QR code. Try again or enter the URL manually.',
'mobile.connect.scan.invalid': 'That QR code is not an OpenChamber connection code.',
@@ -65,6 +67,7 @@ export const dict = {
'mobile.connect.error.authRequired': 'This server needs a password or client token.',
'mobile.connect.error.passwordFailed': 'Could not unlock that server. Check the password.',
'mobile.instances.addTitle': 'Add instance',
'mobile.instances.addManual': 'Add by address',
'mobile.instances.editTitle': 'Edit instance',
'mobile.instances.edit': 'Edit',
'mobile.instances.delete': 'Delete',
@@ -75,6 +78,8 @@ export const dict = {
'mobile.instances.label.label': 'Name',
'mobile.instances.label.placeholder': 'Optional display name',
'mobile.instances.saveNew': 'Save instance',
'mobile.instances.status.connectedDirect': 'Connected · Local network',
'mobile.instances.status.connectedRelay': 'Connected · Private relay',
'mobile.instances.saveEdit': 'Save changes',
'mobile.nav.changes': 'Changes',
'mobile.nav.settings': 'Settings',
+5
View File
@@ -52,7 +52,9 @@ export const dict: Record<I18nKey, string> = {
"mobile.connect.cancelPassword": "Usar otro servidor",
"mobile.connect.connecting": "Conectando...",
"mobile.connect.scanQr": "Escanear código QR",
"mobile.connect.welcome.scanHint": "En tu ordenador, abre «Añadir un dispositivo» para mostrar un código QR y escanéalo aquí.",
"mobile.connect.advanced": "Avanzado",
"mobile.connect.manual.toggle": "Conectar por dirección",
"mobile.connect.scan.permissionDenied": "El acceso a la cámara está desactivado. Actívalo en Ajustes para escanear un código QR.",
"mobile.connect.scan.failed": "No se pudo escanear ese código QR. Inténtalo de nuevo o introduce la URL manualmente.",
"mobile.connect.scan.invalid": "Ese código QR no es un código de conexión de OpenChamber.",
@@ -66,6 +68,7 @@ export const dict: Record<I18nKey, string> = {
"mobile.connect.error.authRequired": "Este servidor requiere una contraseña o un token de cliente.",
"mobile.connect.error.passwordFailed": "No se pudo desbloquear ese servidor. Revisa la contraseña.",
"mobile.instances.addTitle": "Agregar instancia",
"mobile.instances.addManual": "Añadir por dirección",
"mobile.instances.editTitle": "Editar instancia",
"mobile.instances.edit": "Editar",
"mobile.instances.delete": "Eliminar",
@@ -76,6 +79,8 @@ export const dict: Record<I18nKey, string> = {
"mobile.instances.label.label": "Nombre",
"mobile.instances.label.placeholder": "Nombre para mostrar (opcional)",
"mobile.instances.saveNew": "Guardar instancia",
"mobile.instances.status.connectedDirect": "Conectado · Red local",
"mobile.instances.status.connectedRelay": "Conectado · Relay privado",
"mobile.instances.saveEdit": "Guardar cambios",
"mobile.nav.changes": "Cambios",
"mobile.nav.settings": "Ajustes",
+5
View File
@@ -2509,7 +2509,9 @@ export const dict = {
'mobile.connect.cancelPassword': 'Utiliser un autre serveur',
'mobile.connect.connecting': 'Connexion...',
'mobile.connect.scanQr': 'Scanner le code QR',
'mobile.connect.welcome.scanHint': 'Sur votre ordinateur, ouvrez « Ajouter un appareil » pour afficher un code QR, puis scannez-le ici.',
'mobile.connect.advanced': 'Avancé',
'mobile.connect.manual.toggle': 'Connexion par adresse',
'mobile.connect.scan.permissionDenied': 'L\'accès à la caméra est désactivé. Activez-le dans les Réglages pour scanner un code QR.',
'mobile.connect.scan.failed': 'Impossible de scanner ce code QR. Réessayez ou saisissez l\'URL manuellement.',
'mobile.connect.scan.invalid': 'Ce code QR n\'est pas un code de connexion OpenChamber.',
@@ -2523,6 +2525,7 @@ export const dict = {
'mobile.connect.error.authRequired': 'Ce serveur nécessite un mot de passe ou un jeton client.',
'mobile.connect.error.passwordFailed': 'Impossible de déverrouiller ce serveur. Vérifiez le mot de passe.',
'mobile.instances.addTitle': 'Ajouter une instance',
'mobile.instances.addManual': 'Ajouter par adresse',
'mobile.instances.editTitle': 'Modifier l\'instance',
'mobile.instances.edit': 'Modifier',
'mobile.instances.delete': 'Supprimer',
@@ -2533,6 +2536,8 @@ export const dict = {
'mobile.instances.label.label': 'Nom',
'mobile.instances.label.placeholder': 'Nom d\'affichage facultatif',
'mobile.instances.saveNew': 'Enregistrer l\'instance',
'mobile.instances.status.connectedDirect': 'Connecté · Réseau local',
'mobile.instances.status.connectedRelay': 'Connecté · Relais privé',
'mobile.instances.saveEdit': 'Enregistrer les modifications',
'mobile.nav.changes': 'Modifications',
'mobile.nav.settings': 'Paramètres',
+5
View File
@@ -46,7 +46,9 @@ export const dict: Record<I18nKey, string> = {
'mobile.connect.url.label': 'サーバー URL',
'mobile.connect.url.placeholder': 'http://192.168.1.74:2606',
'mobile.connect.scanQr': 'QR コードをスキャン',
'mobile.connect.welcome.scanHint': 'コンピュータで「デバイスを追加」を開いてQRコードを表示し、ここでスキャンしてください。',
'mobile.connect.advanced': '詳細設定',
'mobile.connect.manual.toggle': 'アドレスで接続',
'mobile.connect.token.label': 'クライアントトークン',
'mobile.connect.token.placeholder': 'アクセストークンを貼り付け',
'mobile.connect.token.hint': 'サーバーがパスワードの代わりにトークンを必要とする場合のみ必要です。',
@@ -69,10 +71,13 @@ export const dict: Record<I18nKey, string> = {
'mobile.connect.scan.invalid': 'その QR コードは OpenChamber の接続コードではありません。',
'mobile.connect.scan.failed': 'その QR コードを読み取れませんでした。もう一度試すか、URL を手動で入力してください。',
'mobile.instances.addTitle': 'インスタンスを追加',
'mobile.instances.addManual': 'アドレスで追加',
'mobile.instances.editTitle': 'インスタンスを編集',
'mobile.instances.label.label': '名前',
'mobile.instances.label.placeholder': '表示名(任意)',
'mobile.instances.saveNew': 'インスタンスを保存',
'mobile.instances.status.connectedDirect': '接続中 · ローカルネットワーク',
'mobile.instances.status.connectedRelay': '接続中 · プライベートリレー',
'mobile.instances.saveEdit': '変更を保存',
'mobile.instances.cancelEdit': 'キャンセル',
'mobile.instances.edit': '編集',
+5
View File
@@ -52,7 +52,9 @@ export const dict: Record<I18nKey, string> = {
'mobile.connect.cancelPassword': '다른 서버 사용',
'mobile.connect.connecting': '연결 중...',
'mobile.connect.scanQr': 'QR 코드 스캔',
'mobile.connect.welcome.scanHint': '컴퓨터에서 「기기 추가」를 열어 QR 코드를 표시한 뒤 여기에서 스캔하세요.',
'mobile.connect.advanced': '고급',
'mobile.connect.manual.toggle': '주소로 연결',
'mobile.connect.scan.permissionDenied': '카메라 접근이 꺼져 있습니다. QR 코드를 스캔하려면 설정에서 사용 설정하세요.',
'mobile.connect.scan.failed': 'QR 코드를 스캔하지 못했습니다. 다시 시도하거나 URL을 직접 입력하세요.',
'mobile.connect.scan.invalid': '이 QR 코드는 OpenChamber 연결 코드가 아닙니다.',
@@ -66,6 +68,7 @@ export const dict: Record<I18nKey, string> = {
'mobile.connect.error.authRequired': '이 서버에는 비밀번호 또는 클라이언트 토큰이 필요합니다.',
'mobile.connect.error.passwordFailed': '서버 잠금을 해제할 수 없습니다. 비밀번호를 확인하세요.',
'mobile.instances.addTitle': '인스턴스 추가',
'mobile.instances.addManual': '주소로 추가',
'mobile.instances.editTitle': '인스턴스 편집',
'mobile.instances.edit': '편집',
'mobile.instances.delete': '삭제',
@@ -76,6 +79,8 @@ export const dict: Record<I18nKey, string> = {
'mobile.instances.label.label': '이름',
'mobile.instances.label.placeholder': '표시 이름 (선택 사항)',
'mobile.instances.saveNew': '인스턴스 저장',
'mobile.instances.status.connectedDirect': '연결됨 · 로컬 네트워크',
'mobile.instances.status.connectedRelay': '연결됨 · 비공개 릴레이',
'mobile.instances.saveEdit': '변경 사항 저장',
'mobile.nav.changes': '변경사항',
'mobile.nav.settings': '설정',
+5
View File
@@ -53,7 +53,9 @@ export const dict: Record<I18nKey, string> = {
'mobile.connect.cancelPassword': 'Użyj innego serwera',
'mobile.connect.connecting': 'Łączenie...',
'mobile.connect.scanQr': 'Skanuj kod QR',
'mobile.connect.welcome.scanHint': 'Na komputerze otwórz «Dodaj urządzenie», aby wyświetlić kod QR, i zeskanuj go tutaj.',
'mobile.connect.advanced': 'Zaawansowane',
'mobile.connect.manual.toggle': 'Połącz przez adres',
'mobile.connect.scan.permissionDenied': 'Dostęp do aparatu jest wyłączony. Włącz go w Ustawieniach, aby zeskanować kod QR.',
'mobile.connect.scan.failed': 'Nie udało się zeskanować tego kodu QR. Spróbuj ponownie lub wpisz adres URL ręcznie.',
'mobile.connect.scan.invalid': 'Ten kod QR nie jest kodem połączenia OpenChamber.',
@@ -67,6 +69,7 @@ export const dict: Record<I18nKey, string> = {
'mobile.connect.error.authRequired': 'Ten serwer wymaga hasła lub tokenu klienta.',
'mobile.connect.error.passwordFailed': 'Nie udało się odblokować tego serwera. Sprawdź hasło.',
'mobile.instances.addTitle': 'Dodaj instancję',
'mobile.instances.addManual': 'Dodaj przez adres',
'mobile.instances.editTitle': 'Edytuj instancję',
'mobile.instances.edit': 'Edytuj',
'mobile.instances.delete': 'Usuń',
@@ -77,6 +80,8 @@ export const dict: Record<I18nKey, string> = {
'mobile.instances.label.label': 'Nazwa',
'mobile.instances.label.placeholder': 'Opcjonalna nazwa wyświetlana',
'mobile.instances.saveNew': 'Zapisz instancję',
'mobile.instances.status.connectedDirect': 'Połączono · Sieć lokalna',
'mobile.instances.status.connectedRelay': 'Połączono · Prywatny relay',
'mobile.instances.saveEdit': 'Zapisz zmiany',
'mobile.nav.changes': 'Zmiany',
'mobile.nav.settings': 'Ustawienia',
@@ -52,7 +52,9 @@ export const dict: Record<I18nKey, string> = {
"mobile.connect.cancelPassword": "Usar outro servidor",
"mobile.connect.connecting": "Conectando...",
"mobile.connect.scanQr": "Ler código QR",
"mobile.connect.welcome.scanHint": "No seu computador, abra «Adicionar um dispositivo» para mostrar um código QR e escaneie aqui.",
"mobile.connect.advanced": "Avançado",
"mobile.connect.manual.toggle": "Conectar por endereço",
"mobile.connect.scan.permissionDenied": "O acesso à câmera está desativado. Ative-o nos Ajustes para ler um código QR.",
"mobile.connect.scan.failed": "Não foi possível ler esse código QR. Tente novamente ou digite a URL manualmente.",
"mobile.connect.scan.invalid": "Esse código QR não é um código de conexão do OpenChamber.",
@@ -66,6 +68,7 @@ export const dict: Record<I18nKey, string> = {
"mobile.connect.error.authRequired": "Este servidor requer uma senha ou token do cliente.",
"mobile.connect.error.passwordFailed": "Não foi possível desbloquear esse servidor. Verifique a senha.",
"mobile.instances.addTitle": "Adicionar instância",
"mobile.instances.addManual": "Adicionar por endereço",
"mobile.instances.editTitle": "Editar instância",
"mobile.instances.edit": "Editar",
"mobile.instances.delete": "Excluir",
@@ -76,6 +79,8 @@ export const dict: Record<I18nKey, string> = {
"mobile.instances.label.label": "Nome",
"mobile.instances.label.placeholder": "Nome de exibição opcional",
"mobile.instances.saveNew": "Salvar instância",
"mobile.instances.status.connectedDirect": "Conectado · Rede local",
"mobile.instances.status.connectedRelay": "Conectado · Relay privado",
"mobile.instances.saveEdit": "Salvar alterações",
"mobile.nav.changes": "Alterações",
"mobile.nav.settings": "Configurações",
+5
View File
@@ -52,7 +52,9 @@ export const dict: Record<I18nKey, string> = {
"mobile.connect.cancelPassword": "Інший сервер",
"mobile.connect.connecting": "Підключення...",
"mobile.connect.scanQr": "Сканувати QR-код",
"mobile.connect.welcome.scanHint": "На компʼютері відкрийте «Додати пристрій», щоб показати QR-код, і відскануйте його тут.",
"mobile.connect.advanced": "Додатково",
"mobile.connect.manual.toggle": "Підключитися за адресою",
"mobile.connect.scan.permissionDenied": "Доступ до камери вимкнено. Увімкни його в Налаштуваннях, щоб сканувати QR-код.",
"mobile.connect.scan.failed": "Не вдалося відсканувати QR-код. Спробуй ще раз або введи адресу вручну.",
"mobile.connect.scan.invalid": "Це не QR-код підключення OpenChamber.",
@@ -66,6 +68,7 @@ export const dict: Record<I18nKey, string> = {
"mobile.connect.error.authRequired": "Цьому серверу потрібен пароль або client token.",
"mobile.connect.error.passwordFailed": "Не вдалося розблокувати сервер. Перевір пароль.",
"mobile.instances.addTitle": "Додати інстанс",
"mobile.instances.addManual": "Додати за адресою",
"mobile.instances.editTitle": "Редагувати інстанс",
"mobile.instances.edit": "Редагувати",
"mobile.instances.delete": "Видалити",
@@ -76,6 +79,8 @@ export const dict: Record<I18nKey, string> = {
"mobile.instances.label.label": "Назва",
"mobile.instances.label.placeholder": "Необовʼязкова назва",
"mobile.instances.saveNew": "Зберегти інстанс",
"mobile.instances.status.connectedDirect": "Підключено · Локальна мережа",
"mobile.instances.status.connectedRelay": "Підключено · Приватний relay",
"mobile.instances.saveEdit": "Зберегти зміни",
"mobile.nav.changes": "Зміни",
"mobile.nav.settings": "Налаштування",
@@ -52,7 +52,9 @@ export const dict: Record<I18nKey, string> = {
'mobile.connect.cancelPassword': '使用其他服务器',
'mobile.connect.connecting': '连接中...',
'mobile.connect.scanQr': '扫描二维码',
'mobile.connect.welcome.scanHint': '在电脑上打开「添加设备」显示二维码,然后在这里扫描。',
'mobile.connect.advanced': '高级',
'mobile.connect.manual.toggle': '通过地址连接',
'mobile.connect.scan.permissionDenied': '相机访问已关闭。请在“设置”中开启以扫描二维码。',
'mobile.connect.scan.failed': '无法扫描该二维码。请重试或手动输入网址。',
'mobile.connect.scan.invalid': '该二维码不是 OpenChamber 连接码。',
@@ -66,6 +68,7 @@ export const dict: Record<I18nKey, string> = {
'mobile.connect.error.authRequired': '该服务器需要密码或客户端令牌。',
'mobile.connect.error.passwordFailed': '无法解锁该服务器。请检查密码。',
'mobile.instances.addTitle': '添加实例',
'mobile.instances.addManual': '通过地址添加',
'mobile.instances.editTitle': '编辑实例',
'mobile.instances.edit': '编辑',
'mobile.instances.delete': '删除',
@@ -76,6 +79,8 @@ export const dict: Record<I18nKey, string> = {
'mobile.instances.label.label': '名称',
'mobile.instances.label.placeholder': '可选显示名称',
'mobile.instances.saveNew': '保存实例',
'mobile.instances.status.connectedDirect': '已连接 · 局域网',
'mobile.instances.status.connectedRelay': '已连接 · 私有中继',
'mobile.instances.saveEdit': '保存更改',
'mobile.nav.changes': '更改',
'mobile.nav.settings': '设置',
@@ -52,7 +52,9 @@ export const dict: Record<I18nKey, string> = {
'mobile.connect.cancelPassword': '使用其他伺服器',
'mobile.connect.connecting': '連線中...',
'mobile.connect.scanQr': '掃描 QR code',
'mobile.connect.welcome.scanHint': '在電腦上開啟「新增裝置」顯示 QR 代碼,然後在這裡掃描。',
'mobile.connect.advanced': '進階',
'mobile.connect.manual.toggle': '透過位址連線',
'mobile.connect.scan.permissionDenied': '相機存取已關閉。請在「設定」中開啟以掃描 QR code。',
'mobile.connect.scan.failed': '無法掃描該 QR code。請重試或手動輸入網址。',
'mobile.connect.scan.invalid': '此 QR code 不是 OpenChamber 連線代碼。',
@@ -66,6 +68,7 @@ export const dict: Record<I18nKey, string> = {
'mobile.connect.error.authRequired': '此伺服器需要密碼或用戶端權杖。',
'mobile.connect.error.passwordFailed': '無法解鎖該伺服器。請檢查密碼。',
'mobile.instances.addTitle': '新增執行個體',
'mobile.instances.addManual': '透過位址新增',
'mobile.instances.editTitle': '編輯執行個體',
'mobile.instances.edit': '編輯',
'mobile.instances.delete': '刪除',
@@ -76,6 +79,8 @@ export const dict: Record<I18nKey, string> = {
'mobile.instances.label.label': '名稱',
'mobile.instances.label.placeholder': '選填顯示名稱',
'mobile.instances.saveNew': '儲存執行個體',
'mobile.instances.status.connectedDirect': '已連線 · 區域網路',
'mobile.instances.status.connectedRelay': '已連線 · 私人中繼',
'mobile.instances.saveEdit': '儲存變更',
'mobile.nav.changes': '變更',
'mobile.nav.settings': '設定',