fix(app): Such-Reihenfolge + About-Escape + GPS-Heartbeat fuer near()
(1) Such-Treffer jetzt neueste zuerst (analog WhatsApp/Telegram). User
ist visuell unten, der erste Sprung landet meist im Viewport ohne
weiten Pre-Scroll (= weniger Cold-Start-Fail-Risiko). „Naechster"
geht in die Vergangenheit. Plus Pre-Scroll-Wartezeit 80→200 ms damit
FlatList beim ersten Versuch wirklich Zeit zum Rendern hat.
(2) SettingsScreen Ueber-Text: `—` wurde literal gerendert weil
JSX-Text-Knoten keine JS-String-Escapes interpretieren. Fix:
`{'—'}` als JS-Expression-Block.
(3) GPS-Tracking sendete nach der initialen Position nichts mehr wenn
der User stationaer war — `distanceFilter: 30` blockiert
watchPosition-Updates ohne Bewegung. Nach 5 min (NEAR_MAX_AGE_SEC)
verwirft das Brain die Position als veraltet → near()-Watcher feuern
nie. Stefan's DRK-Trigger waren so chronisch tot.
Fix: zusaetzlich zum watchPosition laeuft ein setInterval(60s)
Heartbeat der die zuletzt empfangene Position erneut sendet. Kein
extra GPS-Wakeup — akkufreundlich. Damit bleibt der Brain-State
frisch auch bei stationaerem User; near() funktioniert sobald der
User tatsaechlich im Radius ist.
Anmerkung zu Stefan's konkretem Test: er war 1.5–2 km von den DRK-
Triggern entfernt (Radius je 300 m) — selbst mit frischen GPS-Updates
haetten die nicht gefeuert. Der Heartbeat-Fix ist trotzdem noetig
damit Trigger ueberhaupt eine Chance haben wenn er tatsaechlich dort
vorbeifaehrt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1381,18 +1381,21 @@ const ChatScreen: React.FC = () => {
|
||||
);
|
||||
const invertedMessages = useMemo(() => [...chatVisibleMessages].reverse(), [chatVisibleMessages]);
|
||||
|
||||
// Such-Treffer: alle Message-IDs die zur Query passen, in chronologischer
|
||||
// Reihenfolge (aelteste zuerst). Bei Query-Change resetten wir den Index.
|
||||
// Such-Treffer: alle Message-IDs die zur Query passen. NEUESTE ZUERST —
|
||||
// analog zu WhatsApp/Telegram: User ist visuell unten im Chat, der erste
|
||||
// Treffer ist meist schon im Viewport (kein weiter Pre-Scroll, kein
|
||||
// Cold-Start-Sprung-Fail). „Naechster" geht in die Vergangenheit.
|
||||
// WICHTIG: nur in chatVisibleMessages suchen — Spezial-Bubbles (Memory/
|
||||
// Skill/Trigger) sind im Chat-Stream nicht sichtbar und Treffer auf die
|
||||
// wuerden zu „ID nicht im FlatList → findIndex=-1 → kein Scroll"-Fail
|
||||
// fuehren (Cessna in einer Memory-Bubble → springt zur falschen Stelle).
|
||||
// fuehren.
|
||||
const searchMatchIds = useMemo(() => {
|
||||
const q = searchQuery.trim().toLowerCase();
|
||||
if (!q) return [] as string[];
|
||||
return chatVisibleMessages
|
||||
.filter(m => (m.text || '').toLowerCase().includes(q))
|
||||
.map(m => m.id);
|
||||
.map(m => m.id)
|
||||
.reverse();
|
||||
}, [chatVisibleMessages, searchQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1470,7 +1473,9 @@ const ChatScreen: React.FC = () => {
|
||||
animated: false,
|
||||
});
|
||||
} catch {}
|
||||
// Nach kurzer Render-Pause praezise nachsetzen
|
||||
// Nach kurzer Render-Pause praezise nachsetzen. 200 ms statt 80 ms —
|
||||
// bei Cold-Start braucht FlatList laenger fuer das Item-Layout, das
|
||||
// war Stefans „erst beim zweiten Versuch klappt's"-Bug.
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
@@ -1478,7 +1483,7 @@ const ChatScreen: React.FC = () => {
|
||||
} catch {
|
||||
// onScrollToIndexFailed-Handler uebernimmt den Fallback
|
||||
}
|
||||
}, 80);
|
||||
}, 200);
|
||||
});
|
||||
}, [searchIndex, searchMatchIds]);
|
||||
|
||||
|
||||
@@ -1798,7 +1798,7 @@ const SettingsScreen: React.FC = () => {
|
||||
<Text style={styles.aboutTitle}>ARIA Cockpit</Text>
|
||||
<Text style={styles.aboutVersion}>Version {require('../../package.json').version}</Text>
|
||||
<Text style={styles.aboutInfo}>
|
||||
ARIA \u2014 Autonomous Reasoning & Intelligence Assistant.{'\n'}
|
||||
ARIA {'\u2014'} Autonomous Reasoning & Intelligence Assistant.{'\n'}
|
||||
Stefans Kommandozentrale.{'\n'}
|
||||
Gebaut mit React Native + TypeScript.
|
||||
</Text>
|
||||
|
||||
@@ -26,6 +26,13 @@ class GpsTrackingService {
|
||||
private listeners: Set<Listener> = new Set();
|
||||
// Defensive: nicht zu schnell oeffentlich togglen
|
||||
private lastChangeAt = 0;
|
||||
// Letzte bekannte Position — wird vom Heartbeat-Timer alle 60s erneut
|
||||
// an die Bridge gesendet, sonst veraltet near() im Brain (NEAR_MAX_AGE_SEC
|
||||
// = 5 min) wenn der User stationaer ist und distanceFilter keine Updates
|
||||
// mehr triggert.
|
||||
private lastLat: number | null = null;
|
||||
private lastLon: number | null = null;
|
||||
private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
@@ -84,6 +91,8 @@ class GpsTrackingService {
|
||||
(pos) => {
|
||||
const lat = pos.coords.latitude;
|
||||
const lon = pos.coords.longitude;
|
||||
this.lastLat = lat;
|
||||
this.lastLon = lon;
|
||||
rvs.send('location_update' as any, { lat, lon });
|
||||
},
|
||||
(err) => {
|
||||
@@ -96,6 +105,17 @@ class GpsTrackingService {
|
||||
fastestInterval: 10000, // (Android) max Frequenz
|
||||
} as any,
|
||||
);
|
||||
// Heartbeat: alle 60s die letzte bekannte Position erneut senden.
|
||||
// Sonst bleibt der Brain-State stale wenn der User stationaer ist
|
||||
// (distanceFilter blockt watchPosition-Updates) → near()-Watcher
|
||||
// verwerfen die Position als veraltet (NEAR_MAX_AGE_SEC = 300s).
|
||||
// Kein neuer GPS-Wakeup, nur Re-Send der letzten Werte → akkufreundlich.
|
||||
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (this.lastLat != null && this.lastLon != null) {
|
||||
rvs.send('location_update' as any, { lat: this.lastLat, lon: this.lastLon });
|
||||
}
|
||||
}, 60_000);
|
||||
this.active = true;
|
||||
this.lastChangeAt = Date.now();
|
||||
this.notify();
|
||||
@@ -118,6 +138,10 @@ class GpsTrackingService {
|
||||
try { Geolocation.clearWatch(this.watchId); } catch {}
|
||||
this.watchId = null;
|
||||
}
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = null;
|
||||
}
|
||||
this.active = false;
|
||||
this.lastChangeAt = Date.now();
|
||||
this.notify();
|
||||
|
||||
Reference in New Issue
Block a user