feat: show android update toast

This commit is contained in:
Bohdan Triapitsyn
2026-07-07 13:49:26 +03:00
parent 4a62a2aeab
commit 1c44146a4f
12 changed files with 102 additions and 0 deletions
+2
View File
@@ -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) {
<div className="h-full bg-background text-foreground">
<SyncAppEffects embeddedBackgroundWorkEnabled={isInitialized} />
<OpenCodeUpdateToast />
<MobileAppUpdateToast />
<MobileShell onActiveConnectionDeleted={() => {
switchRuntimeEndpoint({ apiBaseUrl: '', clientToken: null, runtimeKey: 'mobile-disconnected' });
setConnectionEpoch((value) => value + 1);
@@ -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<string>());
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: <Icon name="download" className="h-4 w-4 text-muted-foreground" />,
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;
};
+4
View File
@@ -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',
+4
View File
@@ -2564,6 +2564,10 @@ export const dict: Record<I18nKey, string> = {
"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",
+4
View File
@@ -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',
+4
View File
@@ -2594,6 +2594,10 @@ export const dict: Record<I18nKey, string> = {
'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': '更新',
+4
View File
@@ -2598,6 +2598,10 @@ export const dict: Record<I18nKey, string> = {
'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': '업데이트',
+4
View File
@@ -2567,6 +2567,10 @@ export const dict: Record<I18nKey, string> = {
'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...',
@@ -2564,6 +2564,10 @@ export const dict: Record<I18nKey, string> = {
"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",
+4
View File
@@ -2564,6 +2564,10 @@ export const dict: Record<I18nKey, string> = {
"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": "Оновити",
@@ -2564,6 +2564,10 @@ export const dict: Record<I18nKey, string> = {
'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': '更新',
@@ -2561,6 +2561,10 @@ export const dict: Record<I18nKey, string> = {
'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': '更新',