From 4d787b68b49b7b8c613f26c095a4859ed0a7fcf4 Mon Sep 17 00:00:00 2001 From: byGalax Date: Thu, 14 May 2026 06:13:25 +0200 Subject: [PATCH] feat(mobile): in-call screen with participants + toolbar --- apps/mobile/app/(app)/call.tsx | 165 +++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 apps/mobile/app/(app)/call.tsx 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' }, +});