/** * Contact requests screen — DChain explorer design style. */ import React, { useState } from 'react'; import { View, Text, FlatList, Alert, TouchableOpacity } from 'react-native'; import { router } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useStore } from '@/lib/store'; import { buildAcceptContactTx, submitTx, getIdentity } from '@/lib/api'; import { saveContact } from '@/lib/storage'; import { shortAddr } from '@/lib/crypto'; import { relativeTime } from '@/lib/utils'; import { Avatar } from '@/components/ui/Avatar'; import type { ContactRequest } from '@/lib/types'; const C = { bg: '#0b1220', surface: '#111a2b', surface2:'#162035', line: '#1c2840', text: '#e6edf9', muted: '#98a7c2', accent: '#7db5ff', ok: '#41c98a', warn: '#f0b35a', err: '#ff7a87', } as const; export default function RequestsScreen() { const keyFile = useStore(s => s.keyFile); const requests = useStore(s => s.requests); const setRequests = useStore(s => s.setRequests); const upsertContact = useStore(s => s.upsertContact); const insets = useSafeAreaInsets(); const [accepting, setAccepting] = useState(null); async function accept(req: ContactRequest) { if (!keyFile) return; setAccepting(req.txHash); try { // Fetch requester's identity to get their x25519 key for messaging const identity = await getIdentity(req.from); const x25519Pub = identity?.x25519_pub ?? ''; const tx = buildAcceptContactTx({ from: keyFile.pub_key, to: req.from, privKey: keyFile.priv_key, }); await submitTx(tx); // Save contact with x25519 key (empty if they haven't registered one) const contact = { address: req.from, x25519Pub, username: req.username, addedAt: Date.now(), }; upsertContact(contact); await saveContact(contact); setRequests(requests.filter(r => r.txHash !== req.txHash)); Alert.alert('Принято', `${req.username ? '@' + req.username : shortAddr(req.from)} добавлен в контакты.`); } catch (e: any) { Alert.alert('Ошибка', e.message); } finally { setAccepting(null); } } function decline(req: ContactRequest) { Alert.alert( 'Отклонить запрос', `Отклонить запрос от ${req.username ? '@' + req.username : shortAddr(req.from)}?`, [ { text: 'Отмена', style: 'cancel' }, { text: 'Отклонить', style: 'destructive', onPress: () => setRequests(requests.filter(r => r.txHash !== req.txHash)), }, ], ); } return ( {/* Header */} router.back()} activeOpacity={0.6} style={{ padding: 8 }} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} > Запросы контактов {requests.length > 0 && ( {requests.length} )} {requests.length === 0 ? ( Нет входящих запросов Когда кто-то пришлёт вам запрос в контакты, он появится здесь. ) : ( r.txHash} contentContainerStyle={{ padding: 14, paddingBottom: 24, gap: 12 }} renderItem={({ item: req }) => ( accept(req)} onDecline={() => decline(req)} /> )} /> )} ); } function RequestCard({ req, isAccepting, onAccept, onDecline, }: { req: ContactRequest; isAccepting: boolean; onAccept: () => void; onDecline: () => void; }) { const displayName = req.username ? `@${req.username}` : shortAddr(req.from); return ( {/* Sender info */} {displayName} {req.from} {relativeTime(req.timestamp)} {/* Intro message */} {!!req.intro && ( Приветствие {req.intro} )} {/* Divider */} {/* Actions */} {isAccepting ? 'Принятие…' : 'Принять'} Отклонить ); }