diff --git a/android/src/screens/SettingsScreen.tsx b/android/src/screens/SettingsScreen.tsx
index 5eee6b5..d76c665 100644
--- a/android/src/screens/SettingsScreen.tsx
+++ b/android/src/screens/SettingsScreen.tsx
@@ -2539,8 +2539,7 @@ const SettingsScreen: React.FC = () => {
style={[styles.connectButton, {marginTop: 12}]}
onPress={() => {
const updateService = require('../services/updater').default;
- updateService.checkForUpdate();
- Alert.alert('Update-Check', 'Pruefe auf neue Version...');
+ updateService.checkForUpdateManual();
}}
>
Auf Updates pr{'\u00FC'}fen
diff --git a/android/src/services/updater.ts b/android/src/services/updater.ts
index 2ed1a9c..044bd66 100644
--- a/android/src/services/updater.ts
+++ b/android/src/services/updater.ts
@@ -50,6 +50,9 @@ class UpdateService {
private progressListeners: ProgressCallback[] = [];
private progress: UpdateProgress = { active: false, version: '', fraction: -1, receivedMB: 0, totalMB: 0, status: '' };
private jobId: number | null = null;
+ /** Laeuft gerade eine Pruefung per Knopf? Dann geht die Antwort NUR dorthin
+ * (sonst zeigt der ChatScreen-Listener denselben Dialog ein zweites Mal). */
+ private manualWaiter: ((r: { type: 'available'; info: UpdateInfo } | { type: 'none'; latest: string }) => void) | null = null;
private cancelled = false;
constructor() {
@@ -66,10 +69,19 @@ class UpdateService {
downloadUrl: (msg.payload.downloadUrl as string) || '',
size: (msg.payload.size as number) || 0,
};
+ if (this.manualWaiter) {
+ const w = this.manualWaiter; this.manualWaiter = null;
+ if (info.version && this.isNewer(info.version)) w({ type: 'available', info });
+ else w({ type: 'none', latest: info.version });
+ return;
+ }
if (info.version && this.isNewer(info.version)) {
console.log(`[Update] Neue Version verfuegbar: ${info.version} (aktuell: ${APP_VERSION})`);
this.listeners.forEach(cb => cb(info));
}
+ } else if ((msg.type as string) === 'update_none' && this.manualWaiter) {
+ const w = this.manualWaiter; this.manualWaiter = null;
+ w({ type: 'none', latest: String(msg.payload.latest || '') });
}
});
}
@@ -150,6 +162,64 @@ class UpdateService {
setTimeout(() => { this.checking = false; }, 10000);
}
+ /**
+ * Pruefung per Knopf (Einstellungen → Ueber): sagt IMMER etwas —
+ * neue Version (Update-Dialog), "keine neue Version" mit der Version auf dem
+ * Server, oder warum der Update-Server nicht erreichbar ist (mit Details).
+ */
+ async checkForUpdateManual(): Promise {
+ const cfg = rvs.getConfig();
+ const where = cfg ? `${cfg.useTLS ? 'wss' : 'ws'}://${cfg.host}:${cfg.port}` : '(nicht eingerichtet)';
+ if (!cfg) {
+ Alert.alert('Update-Server nicht erreichbar', 'Es ist noch kein Server eingerichtet (Einstellungen → Verbindung).');
+ return;
+ }
+ if (!rvs.isSocketOpen()) {
+ const lastErr = [...rvs.getConnectionLog()].reverse().find(e => e.level === 'error');
+ Alert.alert('Update-Server nicht erreichbar',
+ `Keine Verbindung zu ${where}.\n\nZustand: ${rvs.getState()}`
+ + (lastErr ? `\nLetzter Fehler (${new Date(lastErr.timestamp).toLocaleTimeString()}):\n${lastErr.message}` : ''));
+ return;
+ }
+
+ const result = await new Promise<{ type: 'available'; info: UpdateInfo } | { type: 'none'; latest: string } | { type: 'timeout' }>(resolve => {
+ const t = setTimeout(() => { this.manualWaiter = null; resolve({ type: 'timeout' }); }, 10_000);
+ this.manualWaiter = (r) => { clearTimeout(t); resolve(r); };
+ rvs.send('update_check' as any, { version: APP_VERSION });
+ });
+
+ if (result.type === 'available') {
+ this.promptUpdate(result.info);
+ return;
+ }
+ if (result.type === 'timeout') {
+ Alert.alert('Update-Server antwortet nicht',
+ `Verbunden mit ${where}, aber nach 10 s keine Antwort auf die Update-Anfrage.\n\n`
+ + 'Moeglicherweise laeuft auf dem Server eine alte RVS-Version.');
+ return;
+ }
+
+ // Keine neue Version — zusaetzlich pruefen, ob der Download-Weg funktioniert
+ let dl = '';
+ try {
+ const ctrl = new AbortController();
+ const t = setTimeout(() => ctrl.abort(), 10_000);
+ const r = await fetch(`${cfg.useTLS ? 'https' : 'http'}://${cfg.host}:${cfg.port}/update/latest.apk`,
+ { method: 'HEAD', headers: { 'X-RVS-Token': cfg.token }, signal: ctrl.signal });
+ clearTimeout(t);
+ dl = r.ok ? 'Download-Weg: erreichbar ✓'
+ : r.status === 404 && result.latest ? 'Download-Weg: vom Server noch nicht unterstuetzt (alter RVS)'
+ : r.status === 404 ? 'Download-Weg: erreichbar, aber auf dem Server liegt keine APK'
+ : `Download-Weg: Fehler HTTP ${r.status}`;
+ } catch (e: any) {
+ dl = `Download-Weg: nicht erreichbar (${e?.name === 'AbortError' ? 'Zeitueberschreitung' : (e?.message || e)})`;
+ }
+ Alert.alert('Keine neue Version',
+ `Installiert: ${APP_VERSION}\n`
+ + `Neueste auf dem Server: ${result.latest || '(keine APK hinterlegt)'}\n\n`
+ + `Update-Server: ${where} ✓\n${dl}`);
+ }
+
/** Callback registrieren */
onUpdateAvailable(callback: UpdateCallback): () => void {
this.listeners.push(callback);
diff --git a/rvs/server.js b/rvs/server.js
index 63452d0..c1ef06f 100644
--- a/rvs/server.js
+++ b/rvs/server.js
@@ -27,7 +27,7 @@ const ALLOWED_TYPES = new Set([
"chat", "audio", "file", "location", "mode", "log", "event", "heartbeat",
"file_request", "file_response", "file_saved", "stt_result", "config", "tts_request",
"xtts_request", "xtts_response", "xtts_list_voices", "xtts_voices_list", "voice_upload", "xtts_voice_saved",
- "update_check", "update_available", "update_download", "update_data",
+ "update_check", "update_available", "update_none", "update_download", "update_data",
"agent_activity", "cancel_request", "interject",
"audio_pcm",
"file_from_aria",
@@ -614,6 +614,14 @@ function registerClient(ws, token) {
},
timestamp: Date.now(),
}));
+ } else {
+ // Auch "nichts Neues" beantworten — die App zeigt dann, dass der
+ // Update-Server erreichbar ist und welche Version er hat
+ ws.send(JSON.stringify({
+ type: "update_none",
+ payload: { latest: apkInfo ? apkInfo.version : "", current: clientVersion },
+ timestamp: Date.now(),
+ }));
}
return;
}