import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert, ActivityIndicator, TextInput, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { SafeAreaView } from 'react-native-safe-area-context'; import { router, useLocalSearchParams } from 'expo-router'; import * as Device from 'expo-device'; import { useBLE } from '@/contexts/BLEContext'; import { api } from '@/services/api'; import type { WiFiNetwork } from '@/services/ble'; import { AppColors, BorderRadius, FontSizes, FontWeights, Spacing, Shadows, } from '@/constants/theme'; // Type for device passed via navigation params interface DeviceParam { id: string; name: string; mac: string; wellId?: number; } export default function SetupWiFiScreen() { const { id, devices: devicesParam } = useLocalSearchParams<{ id: string; devices: string; // JSON string of DeviceParam[] }>(); const { getWiFiList, setWiFi, disconnectDevice } = useBLE(); // Parse devices from navigation params const selectedDevices: DeviceParam[] = React.useMemo(() => { if (!devicesParam) return []; try { return JSON.parse(devicesParam); } catch (e) { console.error('[SetupWiFi] Failed to parse devices param:', e); return []; } }, [devicesParam]); // Use first device for WiFi scanning (all devices will use same WiFi) const firstDevice = selectedDevices[0]; const deviceId = firstDevice?.id; const deviceName = firstDevice?.name; const wellId = firstDevice?.wellId?.toString(); const [networks, setNetworks] = useState([]); const [isLoadingNetworks, setIsLoadingNetworks] = useState(false); const [selectedNetwork, setSelectedNetwork] = useState(null); const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [isConnecting, setIsConnecting] = useState(false); useEffect(() => { loadWiFiNetworks(); }, []); const loadWiFiNetworks = async () => { setIsLoadingNetworks(true); try { const wifiList = await getWiFiList(deviceId!); setNetworks(wifiList); } catch (error: any) { console.error('[SetupWiFi] Failed to get WiFi list:', error); Alert.alert('Error', error.message || 'Failed to get WiFi networks. Please try again.'); } finally { setIsLoadingNetworks(false); } }; const handleSelectNetwork = (network: WiFiNetwork) => { setSelectedNetwork(network); setPassword(''); }; const handleConnect = async () => { if (!selectedNetwork) { Alert.alert('Error', 'Please select a WiFi network'); return; } if (!password) { Alert.alert('Error', 'Please enter WiFi password'); return; } setIsConnecting(true); try { // Step 1: Set WiFi on the device via BLE const success = await setWiFi(deviceId!, selectedNetwork.ssid, password); if (!success) { throw new Error('Failed to configure WiFi on sensor'); } // Step 2: Attach device to beneficiary via API (skip in simulator/mock mode) const isSimulator = !Device.isDevice; if (!isSimulator) { const attachResponse = await api.attachDeviceToBeneficiary( id!, parseInt(wellId!, 10), selectedNetwork.ssid, password ); if (!attachResponse.ok) { throw new Error('Failed to attach sensor to beneficiary'); } } else { console.log('[SetupWiFi] Simulator mode - skipping API attach'); } // Step 3: Disconnect BLE connection (sensor will reboot and connect to WiFi) await disconnectDevice(deviceId!); // Success! Alert.alert( 'Success!', `${deviceName} has been configured and attached.\n\nThe sensor will now reboot and connect to "${selectedNetwork.ssid}". This may take a minute.`, [ { text: 'Done', onPress: () => { // Navigate back to Equipment screen router.replace(`/(tabs)/beneficiaries/${id}/equipment` as any); }, }, ] ); } catch (error: any) { console.error('[SetupWiFi] Failed to connect:', error); Alert.alert( 'Connection Failed', error.message || 'Failed to configure WiFi. Please check the password and try again.' ); } finally { setIsConnecting(false); } }; const getSignalStrength = (rssi: number): string => { if (rssi >= -50) return 'Excellent'; if (rssi >= -60) return 'Good'; if (rssi >= -70) return 'Fair'; return 'Weak'; }; const getSignalColor = (rssi: number) => { if (rssi >= -50) return AppColors.success; if (rssi >= -60) return AppColors.info; if (rssi >= -70) return AppColors.warning; return AppColors.error; }; const getSignalIcon = (rssi: number) => { if (rssi >= -50) return 'wifi'; if (rssi >= -60) return 'wifi'; if (rssi >= -70) return 'wifi-outline'; return 'wifi-outline'; }; return ( {/* Header */} { // Disconnect all BLE devices before going back selectedDevices.forEach(d => disconnectDevice(d.id)); router.back(); }} > Setup WiFi {/* Device Info Card */} {selectedDevices.length === 1 ? ( <> {deviceName} Well ID: {wellId} ) : ( <> {selectedDevices.length} Sensors Selected {selectedDevices.map(d => d.name).join(', ')} )} {/* Instructions */} {selectedDevices.length === 1 ? 'Select the WiFi network your sensor should connect to. Make sure the network has internet access.' : `Select the WiFi network for all ${selectedDevices.length} sensors. They will all be configured with the same WiFi credentials.`} {/* WiFi Networks List */} {isLoadingNetworks ? ( Scanning for WiFi networks... ) : ( <> Available Networks ({networks.length}) {networks.length === 0 ? ( No WiFi networks found Try Again ) : ( {networks.map((network, index) => { const isSelected = selectedNetwork?.ssid === network.ssid; return ( handleSelectNetwork(network)} activeOpacity={0.7} > {network.ssid} {getSignalStrength(network.rssi)} ({network.rssi} dBm) {isSelected && ( )} ); })} )} )} {/* Password Input (shown when network selected) */} {selectedNetwork && ( WiFi Password setShowPassword(!showPassword)} > {/* Connect Button */} {isConnecting ? ( <> Connecting... ) : ( <> Connect & Complete Setup )} )} {/* Help Card */} Important • The sensor will reboot after WiFi is configured{'\n'} • It may take up to 1 minute for the sensor to connect{'\n'} • Make sure the WiFi password is correct{'\n'} • The network must have internet access ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: AppColors.background, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: Spacing.md, paddingVertical: Spacing.sm, borderBottomWidth: 1, borderBottomColor: AppColors.border, }, backButton: { padding: Spacing.xs, }, headerTitle: { fontSize: FontSizes.lg, fontWeight: FontWeights.semibold, color: AppColors.textPrimary, }, placeholder: { width: 32, }, content: { flex: 1, }, scrollContent: { padding: Spacing.lg, paddingBottom: Spacing.xxl, }, // Device Card deviceCard: { flexDirection: 'row', alignItems: 'center', backgroundColor: AppColors.surface, borderRadius: BorderRadius.xl, padding: Spacing.lg, marginBottom: Spacing.lg, ...Shadows.sm, }, deviceIcon: { width: 60, height: 60, borderRadius: BorderRadius.lg, backgroundColor: AppColors.primaryLighter, justifyContent: 'center', alignItems: 'center', marginRight: Spacing.md, }, deviceInfo: { flex: 1, }, deviceName: { fontSize: FontSizes.lg, fontWeight: FontWeights.semibold, color: AppColors.textPrimary, marginBottom: 2, }, deviceMeta: { fontSize: FontSizes.sm, color: AppColors.textMuted, }, // Instructions instructionsCard: { backgroundColor: AppColors.infoLight, borderRadius: BorderRadius.lg, padding: Spacing.md, marginBottom: Spacing.lg, }, instructionsText: { fontSize: FontSizes.sm, color: AppColors.info, lineHeight: 20, }, // Loading loadingContainer: { alignItems: 'center', padding: Spacing.xl, backgroundColor: AppColors.surface, borderRadius: BorderRadius.xl, ...Shadows.sm, }, loadingText: { fontSize: FontSizes.base, color: AppColors.textSecondary, marginTop: Spacing.md, }, // Section Header sectionHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: Spacing.md, }, sectionTitle: { fontSize: FontSizes.sm, fontWeight: FontWeights.semibold, color: AppColors.textSecondary, textTransform: 'uppercase', letterSpacing: 0.5, }, refreshButton: { padding: Spacing.xs, }, // Empty State emptyState: { alignItems: 'center', padding: Spacing.xl, backgroundColor: AppColors.surface, borderRadius: BorderRadius.xl, ...Shadows.sm, }, emptyText: { fontSize: FontSizes.base, color: AppColors.textMuted, marginTop: Spacing.md, marginBottom: Spacing.md, }, retryButton: { paddingVertical: Spacing.sm, paddingHorizontal: Spacing.lg, }, retryText: { fontSize: FontSizes.base, fontWeight: FontWeights.semibold, color: AppColors.primary, }, // Networks List networksList: { gap: Spacing.sm, marginBottom: Spacing.lg, }, networkCard: { backgroundColor: AppColors.surface, borderRadius: BorderRadius.lg, padding: Spacing.md, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', ...Shadows.xs, }, networkCardSelected: { borderWidth: 2, borderColor: AppColors.primary, }, networkInfo: { flex: 1, flexDirection: 'row', alignItems: 'center', gap: Spacing.md, }, networkDetails: { flex: 1, }, networkName: { fontSize: FontSizes.base, fontWeight: FontWeights.semibold, color: AppColors.textPrimary, marginBottom: 2, }, signalText: { fontSize: FontSizes.xs, fontWeight: FontWeights.medium, }, // Password Input passwordCard: { backgroundColor: AppColors.surface, borderRadius: BorderRadius.xl, padding: Spacing.lg, marginBottom: Spacing.lg, ...Shadows.sm, }, passwordLabel: { fontSize: FontSizes.sm, fontWeight: FontWeights.semibold, color: AppColors.textSecondary, marginBottom: Spacing.sm, }, passwordInputContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: AppColors.background, borderRadius: BorderRadius.md, borderWidth: 1, borderColor: AppColors.border, marginBottom: Spacing.md, }, passwordInput: { flex: 1, paddingVertical: Spacing.sm, paddingHorizontal: Spacing.md, fontSize: FontSizes.base, color: AppColors.textPrimary, }, togglePasswordButton: { padding: Spacing.md, }, // Connect Button connectButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: AppColors.primary, paddingVertical: Spacing.md, borderRadius: BorderRadius.lg, gap: Spacing.sm, ...Shadows.md, }, connectButtonDisabled: { opacity: 0.5, }, connectButtonText: { fontSize: FontSizes.base, fontWeight: FontWeights.semibold, color: AppColors.white, }, // Help Card helpCard: { backgroundColor: AppColors.infoLight, borderRadius: BorderRadius.lg, padding: Spacing.md, }, helpHeader: { flexDirection: 'row', alignItems: 'center', gap: Spacing.sm, marginBottom: Spacing.xs, }, helpTitle: { fontSize: FontSizes.sm, fontWeight: FontWeights.semibold, color: AppColors.info, }, helpText: { fontSize: FontSizes.sm, color: AppColors.info, lineHeight: 20, }, });