feat(app): Workspace-Umbau Schritt 4 — zoombarer Canvas + Fokus/Uebersicht

Der Chat-Tab hostet ab jetzt den Workspace-Canvas (eine ChatScreen-Instanz,
kein Doppel-Mount). Zwei Ebenen:
- Welt-Ebene (reanimated scale/translate): leichte Thumbnail-Kacheln, 2-Finger
  Pinch-Zoom + 2-Finger-Pan nur in der Uebersicht (gesture-handler).
- Identity-Content-Ebene (Scale 1): schwere Inhalte (ChatScreen + kommende
  WebViews) immer gemountet, nur die fokussierte per display sichtbar → Touch/
  Keyboard bleiben korrekt, nichts remountet beim Fokuswechsel.

Tap auf Kachel = Fokus (voll interaktiv), "⤢ Uebersicht"/Hardware-Back = zurueck
zur Landkarte. Bei nur einer Kachel (reiner Chat) ist diese dauerhaft fokussiert
→ verhaelt sich exakt wie der bisherige Vollbild-Chat.

Neue Dateien unter android/src/workspace/: layout.ts (Kamera-Mathe, Center-
Origin fuer RN 0.73), WorkspaceCanvas.tsx, WorkspaceScreen.tsx, Tile.tsx,
tiles/{ChatTile,CodeEditorTile,VncTile,PreviewTile}.tsx. Editor/VNC sind noch
Platzhalter (Commit 5/7). tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 23:48:04 +02:00
co-authored by Claude Opus 4.8
parent 20c527c8ed
commit 85363b1014
9 changed files with 559 additions and 2 deletions
+15
View File
@@ -0,0 +1,15 @@
/**
* ChatTile — hostet die bestehende ChatScreen unveraendert als Workspace-Kachel.
*
* ChatScreen bleibt genau EINE Instanz (der Workspace-Tab ersetzt den alten
* Chat-Tab) und wird nie beim Fokuswechsel remountet — sie liegt in der
* Identity-Content-Ebene und wird nur per display ein-/ausgeblendet. So
* behaelt sie RVS-Abos, Audio, Queue-State und Keyboard-Verhalten wie bisher.
*/
import React from 'react';
import ChatScreen from '../../screens/ChatScreen';
const ChatTile: React.FC = () => <ChatScreen />;
export default React.memo(ChatTile);
@@ -0,0 +1,30 @@
/**
* CodeEditorTile — Live-Code-Editor (CodeMirror in einer WebView).
* Platzhalter fuer Commit 4; die CodeMirror-Bridge folgt in Commit 5.
*/
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
interface Props {
projectId: string;
}
const CodeEditorTile: React.FC<Props> = () => {
return (
<View style={styles.container}>
<Text style={styles.icon}>📝</Text>
<Text style={styles.text}>Code-Editor</Text>
<Text style={styles.sub}>Wird geladen </Text>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0D0D1A', alignItems: 'center', justifyContent: 'center' },
icon: { fontSize: 64, marginBottom: 16 },
text: { color: '#FFFFFF', fontSize: 18, fontWeight: '700' },
sub: { color: '#9090B0', fontSize: 14, marginTop: 8 },
});
export default CodeEditorTile;
@@ -0,0 +1,40 @@
/**
* PreviewTile — zeigt den letzten Screenshot/Vorschau-Frame eines Code-Projekts
* (z. B. aria-vm screenshot). Fuellt sich, sobald ARIA ein Bild in den
* Vorschau-Kanal legt; bis dahin ein ruhiger Platzhalter.
*
* (Screenshot-Anbindung folgt mit dem QEMU-Track; hier zunaechst die Kachel.)
*/
import React from 'react';
import { Image, StyleSheet, Text, View } from 'react-native';
interface Props {
imageUri?: string;
}
const PreviewTile: React.FC<Props> = ({ imageUri }) => {
return (
<View style={styles.container}>
{imageUri ? (
<Image source={{ uri: imageUri }} style={styles.image} resizeMode="contain" />
) : (
<>
<Text style={styles.icon}>🖼</Text>
<Text style={styles.text}>Noch keine Vorschau</Text>
<Text style={styles.sub}>Screenshots der VM erscheinen hier.</Text>
</>
)}
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0D0D1A', alignItems: 'center', justifyContent: 'center' },
image: { width: '100%', height: '100%' },
icon: { fontSize: 64, marginBottom: 16 },
text: { color: '#FFFFFF', fontSize: 18, fontWeight: '700' },
sub: { color: '#9090B0', fontSize: 14, marginTop: 8 },
});
export default PreviewTile;
+31
View File
@@ -0,0 +1,31 @@
/**
* VncTile — Live-Desktop der QEMU-VM (noVNC in einer WebView, RFB durch RVS).
* Platzhalter fuer Commit 4; der noVNC-Tunnel folgt in Commit 7.
*/
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
interface Props {
projectId: string;
focused: boolean;
}
const VncTile: React.FC<Props> = () => {
return (
<View style={styles.container}>
<Text style={styles.icon}>🖥</Text>
<Text style={styles.text}>Desktop</Text>
<Text style={styles.sub}>Kein Desktop verbunden</Text>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#000000', alignItems: 'center', justifyContent: 'center' },
icon: { fontSize: 64, marginBottom: 16 },
text: { color: '#FFFFFF', fontSize: 18, fontWeight: '700' },
sub: { color: '#9090B0', fontSize: 14, marginTop: 8 },
});
export default VncTile;