feat(mobile): IncomingCallModal with Annehmen/Ablehnen + name resolution
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { chat } from '@chat-app/shared';
|
||||
import type { ConversationSummary } from '@chat-app/shared/chat';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Modal, Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import { Avatar } from './Avatar';
|
||||
import { useCall } from '../lib/callContext';
|
||||
import { supabase } from '../lib/supabase';
|
||||
import { colors } from '../theme/colors';
|
||||
|
||||
// Full-screen modal that surfaces over any route when the CallContext
|
||||
// reports an `incoming` state. Resolves the caller's display name from
|
||||
// the conversation membership; falls back to "Anrufer" otherwise.
|
||||
export function IncomingCallModal() {
|
||||
const { state, acceptIncoming, rejectIncoming } = useCall();
|
||||
const visible = state.kind === 'incoming';
|
||||
|
||||
const [conversation, setConversation] = useState<ConversationSummary | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.kind !== 'incoming') {
|
||||
setConversation(null);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
try {
|
||||
const all = await chat.listConversations(supabase);
|
||||
if (cancelled) return;
|
||||
setConversation(all.find((c) => c.id === state.conversationId) ?? null);
|
||||
} catch {
|
||||
/* swallow */
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [state]);
|
||||
|
||||
if (state.kind !== 'incoming') return null;
|
||||
|
||||
const callerName = (() => {
|
||||
if (!conversation) return 'Anrufer';
|
||||
const m = conversation.members.find((mm) => mm.userId === state.fromUserId);
|
||||
return m?.profile?.displayName ?? m?.profile?.username ?? 'Anrufer';
|
||||
})();
|
||||
|
||||
const conversationTitle =
|
||||
conversation?.type === 'group' ? (conversation.name ?? 'Gruppe') : callerName;
|
||||
|
||||
return (
|
||||
<Modal visible={visible} transparent animationType="fade" onRequestClose={rejectIncoming}>
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.subtitle}>Eingehender Anruf</Text>
|
||||
<Avatar name={callerName} size={96} />
|
||||
<Text style={styles.title}>{callerName}</Text>
|
||||
{conversation?.type === 'group' && (
|
||||
<Text style={styles.subtitle}>in {conversationTitle}</Text>
|
||||
)}
|
||||
<View style={styles.actions}>
|
||||
<Pressable
|
||||
style={[styles.button, styles.buttonReject]}
|
||||
onPress={() => {
|
||||
void rejectIncoming();
|
||||
}}
|
||||
>
|
||||
<Text style={styles.buttonText}>Ablehnen</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={[styles.button, styles.buttonAccept]}
|
||||
onPress={() => {
|
||||
void acceptIncoming();
|
||||
}}
|
||||
>
|
||||
<Text style={styles.buttonText}>Annehmen</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.bg,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 20,
|
||||
paddingHorizontal: 32,
|
||||
},
|
||||
title: { color: colors.text, fontSize: 28, fontWeight: '700' },
|
||||
subtitle: { color: colors.textMuted, fontSize: 14 },
|
||||
actions: {
|
||||
flexDirection: 'row',
|
||||
gap: 24,
|
||||
marginTop: 32,
|
||||
},
|
||||
button: {
|
||||
paddingHorizontal: 28,
|
||||
paddingVertical: 16,
|
||||
borderRadius: 14,
|
||||
minWidth: 140,
|
||||
alignItems: 'center',
|
||||
},
|
||||
buttonAccept: { backgroundColor: colors.success },
|
||||
buttonReject: { backgroundColor: colors.danger },
|
||||
buttonText: { color: colors.text, fontWeight: '700', fontSize: 16 },
|
||||
});
|
||||
Reference in New Issue
Block a user