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 { WiFiStatus } from '@/services/ble'; import { AppColors, BorderRadius, FontSizes, FontWeights, Spacing, Shadows, } from '@/constants/theme'; interface SensorInfo { deviceId: string; wellId: number; mac: string; name: string; status: 'online' | 'warning' | 'offline'; lastSeen: Date; beneficiaryId: string; deploymentId: number; location?: string; description?: string; } export default function DeviceSettingsScreen() { const { id, deviceId } = useLocalSearchParams<{ id: string; deviceId: string }>(); const { connectedDevices, isBLEAvailable, connectDevice, disconnectDevice, getCurrentWiFi, rebootDevice, } = useBLE(); const [sensorInfo, setSensorInfo] = useState(null); const [currentWiFi, setCurrentWiFi] = useState(null); const [isLoadingInfo, setIsLoadingInfo] = useState(true); const [isConnecting, setIsConnecting] = useState(false); const [isLoadingWiFi, setIsLoadingWiFi] = useState(false); const [isRebooting, setIsRebooting] = useState(false); const [isSaving, setIsSaving] = useState(false); // Editable fields const [location, setLocation] = useState(''); const [description, setDescription] = useState(''); const isConnected = connectedDevices.has(deviceId!); useEffect(() => { loadSensorInfo(); }, []); const loadSensorInfo = async () => { setIsLoadingInfo(true); try { // Get sensor info from API const response = await api.getDevicesForBeneficiary(id!); if (!response.ok || !response.data) { throw new Error('Failed to load sensor info'); } const sensor = response.data.find((s: SensorInfo) => s.deviceId === deviceId); if (sensor) { setSensorInfo(sensor); setLocation(sensor.location || ''); setDescription(sensor.description || ''); } else { throw new Error('Sensor not found'); } } catch (error: any) { console.error('[DeviceSettings] Failed to load sensor info:', error); Alert.alert('Error', 'Failed to load sensor information'); router.back(); } finally { setIsLoadingInfo(false); } }; const handleConnect = async () => { if (!sensorInfo) return; setIsConnecting(true); try { const success = await connectDevice(deviceId!); if (!success) { throw new Error('Connection failed'); } // Load WiFi status after connecting loadWiFiStatus(); } catch (error: any) { console.error('[DeviceSettings] Connection failed:', error); Alert.alert('Connection Failed', 'Failed to connect to sensor via Bluetooth.'); } finally { setIsConnecting(false); } }; const loadWiFiStatus = async () => { if (!isConnected) return; setIsLoadingWiFi(true); try { const wifiStatus = await getCurrentWiFi(deviceId!); setCurrentWiFi(wifiStatus); } catch (error: any) { console.error('[DeviceSettings] Failed to get WiFi status:', error); Alert.alert('Error', 'Failed to get WiFi status'); } finally { setIsLoadingWiFi(false); } }; const handleChangeWiFi = () => { if (!isConnected) { Alert.alert('Not Connected', 'Please connect to the sensor via Bluetooth first.'); return; } // Navigate to Setup WiFi screen router.push({ pathname: `/(tabs)/beneficiaries/${id}/setup-wifi` as any, params: { deviceId: deviceId!, deviceName: sensorInfo?.name || '', wellId: sensorInfo?.wellId.toString() || '', }, }); }; const handleReboot = () => { if (!isConnected) { Alert.alert('Not Connected', 'Please connect to the sensor via Bluetooth first.'); return; } Alert.alert( 'Reboot Sensor', 'Are you sure you want to reboot this sensor? It will disconnect from Bluetooth and restart.', [ { text: 'Cancel', style: 'cancel' }, { text: 'Reboot', style: 'destructive', onPress: async () => { setIsRebooting(true); try { await rebootDevice(deviceId!); Alert.alert('Success', 'Sensor is rebooting. It will be back online in a minute.'); router.back(); } catch (error: any) { console.error('[DeviceSettings] Reboot failed:', error); Alert.alert('Error', 'Failed to reboot sensor'); } finally { setIsRebooting(false); } }, }, ] ); }; const handleSaveMetadata = async () => { if (!sensorInfo) return; // Check if anything changed const locationChanged = location !== (sensorInfo.location || ''); const descriptionChanged = description !== (sensorInfo.description || ''); if (!locationChanged && !descriptionChanged) { Alert.alert('No Changes', 'No changes to save.'); return; } setIsSaving(true); try { const updates: { location?: string; description?: string } = {}; if (locationChanged) updates.location = location; if (descriptionChanged) updates.description = description; const response = await api.updateDeviceMetadata(sensorInfo.deviceId, updates); if (!response.ok) { throw new Error(response.error?.message || 'Failed to save'); } // Update local state setSensorInfo({ ...sensorInfo, location, description, }); Alert.alert('Success', 'Device information updated.'); } catch (error: any) { console.error('[DeviceSettings] Save failed:', error); Alert.alert('Error', error.message || 'Failed to save device information'); } finally { setIsSaving(false); } }; const hasUnsavedChanges = () => { if (!sensorInfo) return false; return ( location !== (sensorInfo.location || '') || description !== (sensorInfo.description || '') ); }; const formatLastSeen = (lastSeen: Date): string => { const now = new Date(); const diffMs = now.getTime() - lastSeen.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return 'Just now'; if (diffMins < 60) return `${diffMins} min ago`; if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`; return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`; }; 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; }; if (isLoadingInfo || !sensorInfo) { return ( router.back()}> Sensor Settings Loading sensor info... ); } return ( {/* Header */} router.back()}> Sensor Settings {/* Simulator Warning */} {!isBLEAvailable && ( Running in Simulator - BLE features use mock data )} {/* Sensor Info Card */} {sensorInfo.name} {sensorInfo.status === 'online' ? 'Online' : 'Offline'} {formatLastSeen(sensorInfo.lastSeen)} {/* Details Section */} Device Information Well ID {sensorInfo.wellId} MAC Address {sensorInfo.mac} Deployment ID {sensorInfo.deploymentId} {/* Editable Metadata Section */} Sensor Details Location Description {hasUnsavedChanges() && ( <> {isSaving ? ( ) : ( )} {isSaving ? 'Saving...' : 'Save Changes'} )} {/* BLE Connection Section */} Bluetooth Connection {isConnected ? ( Connected disconnectDevice(deviceId!)} > Disconnect ) : ( {isConnecting ? ( ) : ( )} {isConnecting ? 'Connecting...' : 'Connect via Bluetooth'} )} {/* WiFi Status Section */} {isConnected && ( WiFi Status {isLoadingWiFi ? ( Loading WiFi status... ) : currentWiFi ? ( {currentWiFi.ssid} {getSignalStrength(currentWiFi.rssi)} ({currentWiFi.rssi} dBm) Change WiFi ) : ( Not connected to WiFi Setup WiFi )} )} {/* Actions Section */} {isConnected && ( Actions Refresh WiFi Status {isRebooting ? ( ) : ( )} Reboot Sensor )} {/* Info Card */} About Settings • Connect via Bluetooth to view WiFi status and change settings{'\n'} • Make sure you're within range (about 10 meters) of the sensor{'\n'} • Rebooting will disconnect Bluetooth and restart the sensor ); } 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, }, simulatorWarning: { flexDirection: 'row', alignItems: 'center', backgroundColor: AppColors.warningLight, paddingVertical: Spacing.xs, paddingHorizontal: Spacing.md, gap: Spacing.xs, borderBottomWidth: 1, borderBottomColor: AppColors.warning, }, simulatorWarningText: { fontSize: FontSizes.xs, color: AppColors.warning, fontWeight: FontWeights.medium, flex: 1, }, content: { flex: 1, }, scrollContent: { padding: Spacing.lg, paddingBottom: Spacing.xxl, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', gap: Spacing.md, }, loadingText: { fontSize: FontSizes.base, color: AppColors.textSecondary, }, // Sensor Card sensorCard: { flexDirection: 'row', alignItems: 'center', backgroundColor: AppColors.surface, borderRadius: BorderRadius.xl, padding: Spacing.lg, marginBottom: Spacing.lg, ...Shadows.sm, }, sensorIcon: { width: 80, height: 80, borderRadius: BorderRadius.lg, backgroundColor: AppColors.primaryLighter, justifyContent: 'center', alignItems: 'center', marginRight: Spacing.md, }, sensorInfo: { flex: 1, }, sensorName: { fontSize: FontSizes.xl, fontWeight: FontWeights.bold, color: AppColors.textPrimary, marginBottom: Spacing.xs, }, statusRow: { flexDirection: 'row', alignItems: 'center', gap: 6, }, statusDot: { width: 8, height: 8, borderRadius: 4, }, statusText: { fontSize: FontSizes.sm, color: AppColors.textSecondary, fontWeight: FontWeights.medium, }, statusSeparator: { fontSize: FontSizes.sm, color: AppColors.textMuted, }, lastSeenText: { fontSize: FontSizes.sm, color: AppColors.textMuted, }, // Section section: { marginBottom: Spacing.lg, }, sectionTitle: { fontSize: FontSizes.sm, fontWeight: FontWeights.semibold, color: AppColors.textSecondary, marginBottom: Spacing.md, textTransform: 'uppercase', letterSpacing: 0.5, }, // Details Card detailsCard: { backgroundColor: AppColors.surface, borderRadius: BorderRadius.lg, padding: Spacing.md, ...Shadows.xs, }, detailRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: Spacing.sm, }, detailLabel: { fontSize: FontSizes.sm, color: AppColors.textMuted, }, detailValue: { fontSize: FontSizes.sm, fontWeight: FontWeights.semibold, color: AppColors.textPrimary, }, detailDivider: { height: 1, backgroundColor: AppColors.border, }, // Editable fields editableRow: { paddingVertical: Spacing.sm, }, editableLabel: { fontSize: FontSizes.sm, color: AppColors.textMuted, marginBottom: Spacing.xs, }, editableInput: { fontSize: FontSizes.base, color: AppColors.textPrimary, backgroundColor: AppColors.background, borderRadius: BorderRadius.md, paddingHorizontal: Spacing.md, paddingVertical: Spacing.sm, borderWidth: 1, borderColor: AppColors.border, }, editableInputMultiline: { minHeight: 60, textAlignVertical: 'top', }, saveButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: AppColors.primary, paddingVertical: Spacing.sm, paddingHorizontal: Spacing.md, borderRadius: BorderRadius.md, marginTop: Spacing.sm, gap: Spacing.xs, }, saveButtonText: { fontSize: FontSizes.sm, fontWeight: FontWeights.semibold, color: AppColors.white, }, // BLE Connection connectedCard: { backgroundColor: AppColors.surface, borderRadius: BorderRadius.lg, padding: Spacing.md, ...Shadows.xs, }, connectedHeader: { flexDirection: 'row', alignItems: 'center', gap: Spacing.sm, marginBottom: Spacing.md, }, connectedText: { fontSize: FontSizes.base, fontWeight: FontWeights.semibold, color: AppColors.success, }, disconnectButton: { paddingVertical: Spacing.sm, paddingHorizontal: Spacing.md, borderRadius: BorderRadius.md, borderWidth: 1, borderColor: AppColors.border, alignItems: 'center', }, disconnectButtonText: { fontSize: FontSizes.sm, fontWeight: FontWeights.medium, color: AppColors.textSecondary, }, connectButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: AppColors.primary, paddingVertical: Spacing.md, borderRadius: BorderRadius.lg, gap: Spacing.sm, ...Shadows.sm, }, connectButtonText: { fontSize: FontSizes.base, fontWeight: FontWeights.semibold, color: AppColors.white, }, // WiFi Status loadingWiFiCard: { flexDirection: 'row', alignItems: 'center', gap: Spacing.md, backgroundColor: AppColors.surface, borderRadius: BorderRadius.lg, padding: Spacing.md, ...Shadows.xs, }, loadingWiFiText: { fontSize: FontSizes.sm, color: AppColors.textSecondary, }, wifiCard: { backgroundColor: AppColors.surface, borderRadius: BorderRadius.lg, padding: Spacing.md, ...Shadows.xs, }, wifiHeader: { flexDirection: 'row', alignItems: 'center', gap: Spacing.md, marginBottom: Spacing.md, }, wifiInfo: { flex: 1, }, wifiSSID: { fontSize: FontSizes.base, fontWeight: FontWeights.semibold, color: AppColors.textPrimary, marginBottom: 2, }, wifiSignal: { fontSize: FontSizes.sm, fontWeight: FontWeights.medium, }, changeWiFiButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: Spacing.xs, paddingVertical: Spacing.sm, paddingHorizontal: Spacing.md, borderRadius: BorderRadius.md, borderWidth: 1, borderColor: AppColors.primary, }, changeWiFiText: { fontSize: FontSizes.sm, fontWeight: FontWeights.medium, color: AppColors.primary, }, noWiFiCard: { alignItems: 'center', backgroundColor: AppColors.surface, borderRadius: BorderRadius.lg, padding: Spacing.xl, ...Shadows.xs, }, noWiFiText: { fontSize: FontSizes.base, color: AppColors.textMuted, marginTop: Spacing.md, marginBottom: Spacing.md, }, setupWiFiButton: { paddingVertical: Spacing.sm, paddingHorizontal: Spacing.lg, }, setupWiFiText: { fontSize: FontSizes.base, fontWeight: FontWeights.semibold, color: AppColors.primary, }, // Actions actionsCard: { backgroundColor: AppColors.surface, borderRadius: BorderRadius.lg, padding: Spacing.md, ...Shadows.xs, }, actionButton: { flexDirection: 'row', alignItems: 'center', gap: Spacing.sm, paddingVertical: Spacing.sm, }, actionButtonText: { fontSize: FontSizes.base, fontWeight: FontWeights.medium, color: AppColors.primary, }, actionDivider: { height: 1, backgroundColor: AppColors.border, marginVertical: Spacing.sm, }, // Info Card infoCard: { backgroundColor: AppColors.infoLight, borderRadius: BorderRadius.lg, padding: Spacing.md, }, infoHeader: { flexDirection: 'row', alignItems: 'center', gap: Spacing.sm, marginBottom: Spacing.xs, }, infoTitle: { fontSize: FontSizes.sm, fontWeight: FontWeights.semibold, color: AppColors.info, }, infoText: { fontSize: FontSizes.sm, color: AppColors.info, lineHeight: 20, }, });