/** * Welcome / landing screen. * - Node URL input with live ping + QR scanner * - Create / Import account buttons * Redirects to (app)/chats if key already loaded. */ import React, { useEffect, useState, useCallback } from 'react'; import { View, Text, TextInput, Pressable, ScrollView, Alert, ActivityIndicator, } from 'react-native'; import { router } from 'expo-router'; import { CameraView, useCameraPermissions } from 'expo-camera'; import { useStore } from '@/lib/store'; import { saveSettings } from '@/lib/storage'; import { setNodeUrl, getNetStats } from '@/lib/api'; import { Button } from '@/components/ui/Button'; export default function WelcomeScreen() { const keyFile = useStore(s => s.keyFile); const settings = useStore(s => s.settings); const setSettings = useStore(s => s.setSettings); const [nodeInput, setNodeInput] = useState(''); const [scanning, setScanning] = useState(false); const [checking, setChecking] = useState(false); const [nodeOk, setNodeOk] = useState(null); const [permission, requestPermission] = useCameraPermissions(); useEffect(() => { if (keyFile) router.replace('/(app)/chats'); }, [keyFile]); useEffect(() => { setNodeInput(settings.nodeUrl); }, [settings.nodeUrl]); const applyNode = useCallback(async (url: string) => { const clean = url.trim().replace(/\/$/, ''); if (!clean) return; setChecking(true); setNodeOk(null); setNodeUrl(clean); try { await getNetStats(); setNodeOk(true); const next = { ...settings, nodeUrl: clean }; setSettings(next); await saveSettings(next); } catch { setNodeOk(false); } finally { setChecking(false); } }, [settings, setSettings]); const onQrScanned = useCallback(({ data }: { data: string }) => { setScanning(false); let url = data.trim(); try { const p = JSON.parse(url); if (p.nodeUrl) url = p.nodeUrl; } catch {} setNodeInput(url); applyNode(url); }, [applyNode]); const openScanner = async () => { if (!permission?.granted) { const { granted } = await requestPermission(); if (!granted) { Alert.alert('Camera permission required', 'Allow camera access to scan QR codes.'); return; } } setScanning(true); }; // ── QR Scanner overlay ─────────────────────────────────────────────────── if (scanning) { return ( Point at a DChain node QR code setScanning(false)} style={{ position: 'absolute', top: 56, left: 16, backgroundColor: 'rgba(0,0,0,0.6)', borderRadius: 20, paddingHorizontal: 16, paddingVertical: 8, }} > ✕ Cancel ); } // ── Main screen ────────────────────────────────────────────────────────── const statusColor = nodeOk === true ? '#3fb950' : nodeOk === false ? '#f85149' : '#8b949e'; return ( {/* Logo ─ takes remaining space above, centered */} DChain Decentralised E2E-encrypted messenger.{'\n'}Your keys. Your messages. {/* Bottom section ─ node input + buttons */} {/* Node URL label */} Node URL {/* Input row */} {/* Status dot */} { setNodeInput(t); setNodeOk(null); }} onEndEditing={() => applyNode(nodeInput)} onSubmitEditing={() => applyNode(nodeInput)} placeholder="http://192.168.1.10:8081" placeholderTextColor="#8b949e" autoCapitalize="none" autoCorrect={false} keyboardType="url" returnKeyType="done" style={{ flex: 1, color: '#fff', fontSize: 14, paddingVertical: 14 }} /> {checking ? : nodeOk === true ? : nodeOk === false ? : null } {/* QR button */} ({ width: 48, alignItems: 'center', justifyContent: 'center', backgroundColor: '#21262d', borderWidth: 1, borderColor: '#30363d', borderRadius: 12, opacity: pressed ? 0.7 : 1, })} > {/* Status text */} {nodeOk === true && ( ✓ Node connected )} {nodeOk === false && ( ✗ Cannot reach node — check URL and that the node is running )} {/* Buttons */} ); }