diff --git a/apps/mobile/app/(app)/call.tsx b/apps/mobile/app/(app)/call.tsx
new file mode 100644
index 0000000..525f4e9
--- /dev/null
+++ b/apps/mobile/app/(app)/call.tsx
@@ -0,0 +1,165 @@
+import { useRouter } from 'expo-router';
+import { useEffect, useMemo, useState } from 'react';
+import { Pressable, StyleSheet, Text, View } from 'react-native';
+
+import { Avatar } from '../../components/Avatar';
+import { useCall } from '../../lib/callContext';
+import { colors } from '../../theme/colors';
+
+export default function CallScreen() {
+ const router = useRouter();
+ const { state, toggleMute, toggleSpeaker, endCall } = useCall();
+ const [seconds, setSeconds] = useState(0);
+
+ useEffect(() => {
+ if (state.kind !== 'connected') return;
+ setSeconds(0);
+ const id = setInterval(() => setSeconds((s) => s + 1), 1000);
+ return () => clearInterval(id);
+ }, [state.kind]);
+
+ useEffect(() => {
+ if (state.kind === 'idle') {
+ router.back();
+ }
+ }, [state.kind, router]);
+
+ const duration = useMemo(() => {
+ const m = Math.floor(seconds / 60);
+ const s = seconds % 60;
+ return [m, s].map((n) => String(n).padStart(2, '0')).join(':');
+ }, [seconds]);
+
+ if (state.kind === 'idle' || state.kind === 'ended') {
+ return (
+
+ Anruf beendet
+
+ );
+ }
+
+ if (state.kind === 'incoming') {
+ return null;
+ }
+
+ const connecting = state.kind === 'connecting' || state.kind === 'outgoing';
+ const participants = state.kind === 'connected' ? state.participants : [];
+ const muted = state.kind === 'connected' ? state.muted : false;
+ const speakerOn = state.kind === 'connected' ? state.speakerOn : false;
+
+ return (
+
+
+ {connecting ? 'Verbinde ...' : 'Anruf lauft'}
+ {state.kind === 'connected' && {duration}}
+
+
+
+ {participants.length === 0 && (
+ Warten auf Teilnehmer ...
+ )}
+ {participants.map((p) => (
+
+
+
+ {p.name}
+
+ {p.speaking ? 'Spricht ...' : 'Stumm'}
+
+
+
+ ))}
+
+
+
+ {
+ void toggleMute();
+ }}
+ />
+ {
+ void toggleSpeaker();
+ }}
+ />
+ {
+ void endCall();
+ }}
+ >
+ Auflegen
+
+
+
+ );
+}
+
+function ToolbarButton({
+ label,
+ active,
+ onPress,
+}: {
+ label: string;
+ active: boolean;
+ onPress: () => void;
+}) {
+ return (
+
+
+ {label}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: { flex: 1, backgroundColor: colors.bg, padding: 24 },
+ header: { alignItems: 'center', marginTop: 24, gap: 6 },
+ title: { color: colors.text, fontSize: 24, fontWeight: '700' },
+ duration: { color: colors.textMuted, fontSize: 16, fontVariant: ['tabular-nums'] },
+ participantList: { flex: 1, marginTop: 24, gap: 12 },
+ empty: { color: colors.textMuted, textAlign: 'center', marginTop: 32 },
+ participantRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 12,
+ backgroundColor: colors.surface,
+ padding: 12,
+ borderRadius: 12,
+ borderColor: colors.border,
+ borderWidth: 1,
+ },
+ participantText: { flex: 1 },
+ participantName: { color: colors.text, fontSize: 16, fontWeight: '600' },
+ participantStatus: { color: colors.textMuted, fontSize: 12, marginTop: 2 },
+ participantSpeaking: { color: colors.success, fontWeight: '700' },
+ toolbar: { flexDirection: 'row', gap: 12, paddingBottom: 16 },
+ toolbarButton: {
+ flex: 1,
+ paddingVertical: 14,
+ borderRadius: 14,
+ backgroundColor: colors.surface,
+ borderColor: colors.border,
+ borderWidth: 1,
+ alignItems: 'center',
+ },
+ toolbarButtonActive: { backgroundColor: colors.accentMuted, borderColor: colors.accent },
+ toolbarButtonText: { color: colors.text, fontWeight: '600' },
+ toolbarButtonTextActive: { color: colors.accent },
+ hangup: {
+ flex: 1,
+ paddingVertical: 14,
+ borderRadius: 14,
+ backgroundColor: colors.danger,
+ alignItems: 'center',
+ },
+ hangupText: { color: colors.text, fontWeight: '700' },
+});