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