diff --git a/packages/ui/src/apps/MobileApp.tsx b/packages/ui/src/apps/MobileApp.tsx index bc7f9a2c..3d5432fd 100644 --- a/packages/ui/src/apps/MobileApp.tsx +++ b/packages/ui/src/apps/MobileApp.tsx @@ -6,6 +6,7 @@ import { McpIcon } from '@/components/icons/McpIcon'; import { McpDropdownContent } from '@/components/mcp/McpDropdown'; import { AboutSettings } from '@/components/sections/openchamber/AboutSettings'; import { OpenCodeUpdateToast } from '@/components/update/OpenCodeUpdateToast'; +import { MobileAppUpdateToast } from '@/components/update/MobileAppUpdateToast'; import { ConfigUpdateOverlay } from '@/components/ui/ConfigUpdateOverlay'; import { Button } from '@/components/ui/button'; import { OpenChamberLogo } from '@/components/ui/OpenChamberLogo'; @@ -2485,6 +2486,7 @@ export function MobileApp({ apis }: MobileAppProps) {
+ { switchRuntimeEndpoint({ apiBaseUrl: '', clientToken: null, runtimeKey: 'mobile-disconnected' }); setConnectionEpoch((value) => value + 1); diff --git a/packages/ui/src/components/update/MobileAppUpdateToast.tsx b/packages/ui/src/components/update/MobileAppUpdateToast.tsx new file mode 100644 index 00000000..24983f31 --- /dev/null +++ b/packages/ui/src/components/update/MobileAppUpdateToast.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; + +import { Icon } from '@/components/icon/Icon'; +import { toast } from '@/components/ui/toast'; +import { useI18n } from '@/lib/i18n'; +import { getClientPlatform } from '@/lib/platform'; +import { openExternalUrl } from '@/lib/url'; +import { useUpdateStore } from '@/stores/useUpdateStore'; +import { getDeferredSafeStorage } from '@/stores/utils/safeStorage'; + +const TOAST_ID = 'mobile-app-update-available'; +const DISMISSED_VERSION_KEY = 'mobile-app-update-toast-dismissed-version'; + +export const MobileAppUpdateToast: React.FC = () => { + const { t } = useI18n(); + const available = useUpdateStore((state) => state.available); + const runtimeType = useUpdateStore((state) => state.runtimeType); + const version = useUpdateStore((state) => state.info?.version); + const downloadUrl = useUpdateStore((state) => state.info?.downloadUrl); + const seenVersionsRef = React.useRef(new Set()); + + React.useEffect(() => { + if (getClientPlatform() !== 'android' || runtimeType !== 'mobile' || !available || !version || !downloadUrl) { + toast.dismiss(TOAST_ID); + return; + } + + if (getDeferredSafeStorage().getItem(DISMISSED_VERSION_KEY) === version) { + return; + } + + if (seenVersionsRef.current.has(version)) { + return; + } + + seenVersionsRef.current.add(version); + + toast.info(t('mobileUpdate.toast.available.title'), { + id: TOAST_ID, + description: t('mobileUpdate.toast.available.description', { version }), + duration: Infinity, + icon: , + action: { + label: t('mobileUpdate.toast.actions.download'), + onClick: () => { + void openExternalUrl(downloadUrl); + }, + }, + cancel: { + label: t('mobileUpdate.toast.actions.dismiss'), + onClick: () => { + getDeferredSafeStorage().setItem(DISMISSED_VERSION_KEY, version); + toast.dismiss(TOAST_ID); + }, + }, + }); + }, [available, downloadUrl, runtimeType, t, version]); + + return null; +}; diff --git a/packages/ui/src/lib/i18n/messages/en.ts b/packages/ui/src/lib/i18n/messages/en.ts index 3994b998..b2c4d9a0 100644 --- a/packages/ui/src/lib/i18n/messages/en.ts +++ b/packages/ui/src/lib/i18n/messages/en.ts @@ -2598,6 +2598,10 @@ export const dict = { 'updateDialog.status.updating': 'Updating...', 'updateDialog.error.updateFailed': 'Update failed', 'updateDialog.error.takingLonger': 'Update is taking longer than expected. Wait a bit and refresh, or run: openchamber update', + 'mobileUpdate.toast.available.title': 'OpenChamber update available', + 'mobileUpdate.toast.available.description': 'Version {version} is ready for Android.', + 'mobileUpdate.toast.actions.download': 'Download', + 'mobileUpdate.toast.actions.dismiss': 'Dismiss', 'opencodeUpdate.toast.available.title': 'OpenCode update', 'opencodeUpdate.toast.available.description': 'Version {version} available.', 'opencodeUpdate.toast.actions.update': 'Update', diff --git a/packages/ui/src/lib/i18n/messages/es.ts b/packages/ui/src/lib/i18n/messages/es.ts index 1a070aa1..4c1a15d5 100644 --- a/packages/ui/src/lib/i18n/messages/es.ts +++ b/packages/ui/src/lib/i18n/messages/es.ts @@ -2564,6 +2564,10 @@ export const dict: Record = { "updateDialog.status.updating": "Actualizando...", "updateDialog.error.updateFailed": "No se pudo actualizar", "updateDialog.error.takingLonger": "La actualización está tardando más de lo esperado. Espera un poco y refresca, o ejecuta: openchamber update", + "mobileUpdate.toast.available.title": "Actualización de OpenChamber disponible", + "mobileUpdate.toast.available.description": "La versión {version} está lista para Android.", + "mobileUpdate.toast.actions.download": "Descargar", + "mobileUpdate.toast.actions.dismiss": "Descartar", "opencodeUpdate.toast.available.title": "Actualización de OpenCode", "opencodeUpdate.toast.available.description": "Versión {version} disponible.", "opencodeUpdate.toast.actions.update": "Actualizar", diff --git a/packages/ui/src/lib/i18n/messages/fr.ts b/packages/ui/src/lib/i18n/messages/fr.ts index e237e79d..9e247d6d 100644 --- a/packages/ui/src/lib/i18n/messages/fr.ts +++ b/packages/ui/src/lib/i18n/messages/fr.ts @@ -2369,6 +2369,10 @@ export const dict = { 'updateDialog.status.updating': 'Mise à jour...', 'updateDialog.error.updateFailed': 'La mise à jour a échoué', 'updateDialog.error.takingLonger': 'La mise à jour prend plus de temps que prévu. Attendez un peu et actualisez, ou exécutez : openchamber update', + 'mobileUpdate.toast.available.title': 'Mise à jour OpenChamber disponible', + 'mobileUpdate.toast.available.description': 'La version {version} est prête pour Android.', + 'mobileUpdate.toast.actions.download': 'Télécharger', + 'mobileUpdate.toast.actions.dismiss': 'Ignorer', 'opencodeUpdate.toast.available.title': 'Mise à jour OpenCode', 'opencodeUpdate.toast.available.description': 'Version {version} disponible.', 'opencodeUpdate.toast.actions.update': 'Mise à jour', diff --git a/packages/ui/src/lib/i18n/messages/ja.ts b/packages/ui/src/lib/i18n/messages/ja.ts index 792c42a5..78c2f82f 100644 --- a/packages/ui/src/lib/i18n/messages/ja.ts +++ b/packages/ui/src/lib/i18n/messages/ja.ts @@ -2594,6 +2594,10 @@ export const dict: Record = { 'updateDialog.status.updating': '更新中...', 'updateDialog.error.updateFailed': '更新に失敗しました', 'updateDialog.error.takingLonger': '更新に予想以上に時間がかかっています。しばらく待ってから更新するか、次を実行: openchamber update', + 'mobileUpdate.toast.available.title': 'OpenChamberの更新があります', + 'mobileUpdate.toast.available.description': 'バージョン{version}をAndroidで利用できます。', + 'mobileUpdate.toast.actions.download': 'ダウンロード', + 'mobileUpdate.toast.actions.dismiss': '閉じる', 'opencodeUpdate.toast.available.title': 'OpenCode更新', 'opencodeUpdate.toast.available.description': 'バージョン{version}が利用可能です。', 'opencodeUpdate.toast.actions.update': '更新', diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts index 5cac4838..982a49b6 100644 --- a/packages/ui/src/lib/i18n/messages/ko.ts +++ b/packages/ui/src/lib/i18n/messages/ko.ts @@ -2598,6 +2598,10 @@ export const dict: Record = { 'updateDialog.status.updating': '업데이트 중…', 'updateDialog.error.updateFailed': '업데이트 실패', 'updateDialog.error.takingLonger': '업데이트가 예상보다 오래 걸립니다. 잠시 기다린 뒤 새로고침하거나 `openchamber update`를 실행하세요.', + 'mobileUpdate.toast.available.title': 'OpenChamber 업데이트 사용 가능', + 'mobileUpdate.toast.available.description': 'Android용 버전 {version}이 준비되었습니다.', + 'mobileUpdate.toast.actions.download': '다운로드', + 'mobileUpdate.toast.actions.dismiss': '닫기', 'opencodeUpdate.toast.available.title': 'OpenCode 업데이트', 'opencodeUpdate.toast.available.description': '버전 {version} 사용 가능.', 'opencodeUpdate.toast.actions.update': '업데이트', diff --git a/packages/ui/src/lib/i18n/messages/pl.ts b/packages/ui/src/lib/i18n/messages/pl.ts index 0f258195..96e66b5f 100644 --- a/packages/ui/src/lib/i18n/messages/pl.ts +++ b/packages/ui/src/lib/i18n/messages/pl.ts @@ -2567,6 +2567,10 @@ export const dict: Record = { 'updateDialog.actions.updateNow': 'Aktualizuj teraz', 'updateDialog.error.takingLonger': 'Aktualizacja trwa dłużej niż oczekiwano. Poczekaj chwilę i odśwież albo uruchom: openchamber update', 'updateDialog.error.updateFailed': 'Aktualizacja nie powiodła się', + 'mobileUpdate.toast.available.title': 'Dostępna aktualizacja OpenChamber', + 'mobileUpdate.toast.available.description': 'Wersja {version} jest gotowa dla Androida.', + 'mobileUpdate.toast.actions.download': 'Pobierz', + 'mobileUpdate.toast.actions.dismiss': 'Odrzuć', 'updateDialog.fallback.updateViaTerminal': 'Lub zaktualizuj przez terminal:', 'updateDialog.header.updateAvailable': 'Dostępna aktualizacja', 'updateDialog.header.updating': 'Aktualizowanie OpenChamber...', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts index bbff90b3..34ed5122 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts @@ -2564,6 +2564,10 @@ export const dict: Record = { "updateDialog.status.updating": "Atualizando...", "updateDialog.error.updateFailed": "Não foi possível atualizar", "updateDialog.error.takingLonger": "A atualização está demorando mais do que o esperado. Aguarde um pouco e atualize, ou execute: openchamber update", + "mobileUpdate.toast.available.title": "Atualização do OpenChamber disponível", + "mobileUpdate.toast.available.description": "A versão {version} está pronta para Android.", + "mobileUpdate.toast.actions.download": "Baixar", + "mobileUpdate.toast.actions.dismiss": "Dispensar", "opencodeUpdate.toast.available.title": "Atualização do OpenCode", "opencodeUpdate.toast.available.description": "Versão {version} disponível.", "opencodeUpdate.toast.actions.update": "Atualizar", diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts index c063aea4..3575f0ba 100644 --- a/packages/ui/src/lib/i18n/messages/uk.ts +++ b/packages/ui/src/lib/i18n/messages/uk.ts @@ -2564,6 +2564,10 @@ export const dict: Record = { "updateDialog.status.updating": "Оновлення...", "updateDialog.error.updateFailed": "Помилка оновлення", "updateDialog.error.takingLonger": "Оновлення триває довше, ніж очікувалося. Зачекайте трохи та оновіть або запустіть: openchamber update", + "mobileUpdate.toast.available.title": "Доступне оновлення OpenChamber", + "mobileUpdate.toast.available.description": "Версія {version} готова для Android.", + "mobileUpdate.toast.actions.download": "Завантажити", + "mobileUpdate.toast.actions.dismiss": "Сховати", "opencodeUpdate.toast.available.title": "Оновлення OpenCode", "opencodeUpdate.toast.available.description": "Версія {version} доступна.", "opencodeUpdate.toast.actions.update": "Оновити", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts index ce7ea749..12908732 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts @@ -2564,6 +2564,10 @@ export const dict: Record = { 'updateDialog.status.updating': '更新中...', 'updateDialog.error.updateFailed': '更新失败', 'updateDialog.error.takingLonger': '更新耗时超出预期。请稍等后刷新,或运行:openchamber update', + 'mobileUpdate.toast.available.title': 'OpenChamber 更新可用', + 'mobileUpdate.toast.available.description': '版本 {version} 已可用于 Android。', + 'mobileUpdate.toast.actions.download': '下载', + 'mobileUpdate.toast.actions.dismiss': '忽略', 'opencodeUpdate.toast.available.title': 'OpenCode 更新', 'opencodeUpdate.toast.available.description': '版本 {version} 可用。', 'opencodeUpdate.toast.actions.update': '更新', diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.ts b/packages/ui/src/lib/i18n/messages/zh-TW.ts index 926a651c..c241ff23 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.ts @@ -2561,6 +2561,10 @@ export const dict: Record = { 'updateDialog.status.updating': '更新中...', 'updateDialog.error.updateFailed': '更新失敗', 'updateDialog.error.takingLonger': '更新耗時超出預期。請稍等後重新整理,或執行:openchamber update', + 'mobileUpdate.toast.available.title': 'OpenChamber 更新可用', + 'mobileUpdate.toast.available.description': '版本 {version} 已可用於 Android。', + 'mobileUpdate.toast.actions.download': '下載', + 'mobileUpdate.toast.actions.dismiss': '忽略', 'opencodeUpdate.toast.available.title': 'OpenCode 更新', 'opencodeUpdate.toast.available.description': '版本 {version} 可安裝。', 'opencodeUpdate.toast.actions.update': '更新',