Update navigation to pass selected devices array

- add-sensor.tsx now passes devices array with mac address via JSON
- setup-wifi.tsx parses devices from navigation params
- Support batch mode display (shows count and device names)
- Disconnect all devices when navigating back

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sergei 2026-01-19 22:43:12 -08:00
parent 52def3cb79
commit b738d86419
2 changed files with 215 additions and 56 deletions

View File

@ -37,11 +37,40 @@ export default function AddSensorScreen() {
connectDevice, connectDevice,
} = useBLE(); } = useBLE();
const [selectedDevice, setSelectedDevice] = useState<WPDevice | null>(null); const [selectedDevices, setSelectedDevices] = useState<Set<string>>(new Set());
const [isConnecting, setIsConnecting] = useState(false); const [isConnecting, setIsConnecting] = useState(false);
const beneficiaryName = currentBeneficiary?.name || 'this person'; const beneficiaryName = currentBeneficiary?.name || 'this person';
// Select all devices by default when scan completes
React.useEffect(() => {
if (foundDevices.length > 0 && !isScanning) {
setSelectedDevices(new Set(foundDevices.map(d => d.id)));
}
}, [foundDevices, isScanning]);
const toggleDeviceSelection = (deviceId: string) => {
setSelectedDevices(prev => {
const next = new Set(prev);
if (next.has(deviceId)) {
next.delete(deviceId);
} else {
next.add(deviceId);
}
return next;
});
};
const toggleSelectAll = () => {
if (selectedDevices.size === foundDevices.length) {
setSelectedDevices(new Set());
} else {
setSelectedDevices(new Set(foundDevices.map(d => d.id)));
}
};
const selectedCount = selectedDevices.size;
const handleScan = async () => { const handleScan = async () => {
try { try {
await scanDevices(); await scanDevices();
@ -51,36 +80,26 @@ export default function AddSensorScreen() {
} }
}; };
const handleConnect = async (device: WPDevice) => { const handleAddSelected = () => {
setIsConnecting(true); if (selectedCount === 0) {
setSelectedDevice(device); Alert.alert('No Sensors Selected', 'Please select at least one sensor to add.');
return;
try {
const success = await connectDevice(device.id);
if (success) {
// Navigate to Setup WiFi screen
router.push({
pathname: `/(tabs)/beneficiaries/${id}/setup-wifi` as any,
params: {
deviceId: device.id,
deviceName: device.name,
wellId: device.wellId?.toString() || '',
},
});
} else {
throw new Error('Connection failed');
}
} catch (error: any) {
console.error('[AddSensor] Connection failed:', error);
Alert.alert(
'Connection Failed',
`Failed to connect to ${device.name}. Please make sure the sensor is powered on and nearby.`
);
} finally {
setIsConnecting(false);
setSelectedDevice(null);
} }
const devices = foundDevices.filter(d => selectedDevices.has(d.id));
// Navigate to Setup WiFi screen with selected devices
router.push({
pathname: `/(tabs)/beneficiaries/${id}/setup-wifi` as any,
params: {
devices: JSON.stringify(devices.map(d => ({
id: d.id,
name: d.name,
mac: d.mac,
wellId: d.wellId,
}))),
},
});
}; };
const getSignalIcon = (rssi: number) => { const getSignalIcon = (rssi: number) => {
@ -172,28 +191,52 @@ export default function AddSensorScreen() {
<> <>
<View style={styles.sectionHeader}> <View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>Found Sensors ({foundDevices.length})</Text> <Text style={styles.sectionTitle}>Found Sensors ({foundDevices.length})</Text>
<TouchableOpacity style={styles.rescanButton} onPress={handleScan}> <View style={styles.sectionActions}>
<Ionicons name="refresh" size={18} color={AppColors.primary} /> <TouchableOpacity style={styles.selectAllButton} onPress={toggleSelectAll}>
<Text style={styles.rescanText}>Rescan</Text> <Ionicons
</TouchableOpacity> name={selectedDevices.size === foundDevices.length ? 'checkbox' : 'square-outline'}
size={18}
color={AppColors.primary}
/>
<Text style={styles.selectAllText}>
{selectedDevices.size === foundDevices.length ? 'Deselect All' : 'Select All'}
</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.rescanButton} onPress={handleScan}>
<Ionicons name="refresh" size={18} color={AppColors.primary} />
<Text style={styles.rescanText}>Rescan</Text>
</TouchableOpacity>
</View>
</View> </View>
<View style={styles.devicesList}> <View style={styles.devicesList}>
{foundDevices.map((device) => { {foundDevices.map((device) => {
const isConnected = connectedDevices.has(device.id); const isConnected = connectedDevices.has(device.id);
const isConnectingThis = isConnecting && selectedDevice?.id === device.id; const isSelected = selectedDevices.has(device.id);
return ( return (
<TouchableOpacity <TouchableOpacity
key={device.id} key={device.id}
style={[ style={[
styles.deviceCard, styles.deviceCard,
isSelected && styles.deviceCardSelected,
isConnected && styles.deviceCardConnected, isConnected && styles.deviceCardConnected,
]} ]}
onPress={() => handleConnect(device)} onPress={() => toggleDeviceSelection(device.id)}
disabled={isConnectingThis || isConnected} disabled={isConnected}
activeOpacity={0.7} activeOpacity={0.7}
> >
<View style={styles.checkboxContainer}>
<View style={[
styles.checkbox,
isSelected && styles.checkboxSelected,
isConnected && styles.checkboxDisabled,
]}>
{isSelected && (
<Ionicons name="checkmark" size={16} color={AppColors.white} />
)}
</View>
</View>
<View style={styles.deviceInfo}> <View style={styles.deviceInfo}>
<View style={styles.deviceIcon}> <View style={styles.deviceIcon}>
<Ionicons name="water" size={24} color={AppColors.primary} /> <Ionicons name="water" size={24} color={AppColors.primary} />
@ -216,19 +259,30 @@ export default function AddSensorScreen() {
</View> </View>
</View> </View>
{isConnectingThis ? ( {isConnected && (
<ActivityIndicator size="small" color={AppColors.primary} /> <View style={styles.alreadyAddedBadge}>
) : isConnected ? ( <Text style={styles.alreadyAddedText}>Added</Text>
<View style={styles.connectedBadge}>
<Ionicons name="checkmark-circle" size={20} color={AppColors.success} />
</View> </View>
) : (
<Ionicons name="chevron-forward" size={20} color={AppColors.textMuted} />
)} )}
</TouchableOpacity> </TouchableOpacity>
); );
})} })}
</View> </View>
{/* Add Selected Button */}
<TouchableOpacity
style={[
styles.addSelectedButton,
selectedCount === 0 && styles.addSelectedButtonDisabled,
]}
onPress={handleAddSelected}
disabled={selectedCount === 0}
>
<Ionicons name="add-circle" size={24} color={AppColors.white} />
<Text style={styles.addSelectedButtonText}>
Add Selected ({selectedCount})
</Text>
</TouchableOpacity>
</> </>
)} )}
@ -406,6 +460,21 @@ const styles = StyleSheet.create({
textTransform: 'uppercase', textTransform: 'uppercase',
letterSpacing: 0.5, letterSpacing: 0.5,
}, },
sectionActions: {
flexDirection: 'row',
alignItems: 'center',
gap: Spacing.md,
},
selectAllButton: {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
},
selectAllText: {
fontSize: FontSizes.sm,
fontWeight: FontWeights.medium,
color: AppColors.primary,
},
rescanButton: { rescanButton: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
@ -427,12 +496,37 @@ const styles = StyleSheet.create({
padding: Spacing.md, padding: Spacing.md,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between',
...Shadows.xs, ...Shadows.xs,
}, },
deviceCardSelected: {
borderWidth: 2,
borderColor: AppColors.primary,
},
deviceCardConnected: { deviceCardConnected: {
borderWidth: 2, borderWidth: 2,
borderColor: AppColors.success, borderColor: AppColors.success,
opacity: 0.6,
},
checkboxContainer: {
marginRight: Spacing.sm,
},
checkbox: {
width: 24,
height: 24,
borderRadius: BorderRadius.sm,
borderWidth: 2,
borderColor: AppColors.border,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: AppColors.white,
},
checkboxSelected: {
backgroundColor: AppColors.primary,
borderColor: AppColors.primary,
},
checkboxDisabled: {
backgroundColor: AppColors.surfaceSecondary,
borderColor: AppColors.border,
}, },
deviceInfo: { deviceInfo: {
flex: 1, flex: 1,
@ -471,8 +565,37 @@ const styles = StyleSheet.create({
fontSize: FontSizes.xs, fontSize: FontSizes.xs,
fontWeight: FontWeights.medium, fontWeight: FontWeights.medium,
}, },
connectedBadge: { alreadyAddedBadge: {
padding: Spacing.xs, backgroundColor: AppColors.successLight,
paddingVertical: Spacing.xs,
paddingHorizontal: Spacing.sm,
borderRadius: BorderRadius.sm,
},
alreadyAddedText: {
fontSize: FontSizes.xs,
fontWeight: FontWeights.medium,
color: AppColors.success,
},
// Add Selected Button
addSelectedButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: AppColors.primary,
paddingVertical: Spacing.md,
borderRadius: BorderRadius.lg,
marginTop: Spacing.md,
marginBottom: Spacing.lg,
gap: Spacing.sm,
...Shadows.md,
},
addSelectedButtonDisabled: {
backgroundColor: AppColors.textMuted,
},
addSelectedButtonText: {
fontSize: FontSizes.base,
fontWeight: FontWeights.semibold,
color: AppColors.white,
}, },
// Empty State // Empty State
emptyState: { emptyState: {

View File

@ -25,16 +25,39 @@ import {
Shadows, Shadows,
} from '@/constants/theme'; } from '@/constants/theme';
// Type for device passed via navigation params
interface DeviceParam {
id: string;
name: string;
mac: string;
wellId?: number;
}
export default function SetupWiFiScreen() { export default function SetupWiFiScreen() {
const { id, deviceId, deviceName, wellId } = useLocalSearchParams<{ const { id, devices: devicesParam } = useLocalSearchParams<{
id: string; id: string;
deviceId: string; devices: string; // JSON string of DeviceParam[]
deviceName: string;
wellId: string;
}>(); }>();
const { getWiFiList, setWiFi, disconnectDevice } = useBLE(); 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<WiFiNetwork[]>([]); const [networks, setNetworks] = useState<WiFiNetwork[]>([]);
const [isLoadingNetworks, setIsLoadingNetworks] = useState(false); const [isLoadingNetworks, setIsLoadingNetworks] = useState(false);
const [selectedNetwork, setSelectedNetwork] = useState<WiFiNetwork | null>(null); const [selectedNetwork, setSelectedNetwork] = useState<WiFiNetwork | null>(null);
@ -160,8 +183,8 @@ export default function SetupWiFiScreen() {
<TouchableOpacity <TouchableOpacity
style={styles.backButton} style={styles.backButton}
onPress={() => { onPress={() => {
// Disconnect BLE before going back // Disconnect all BLE devices before going back
disconnectDevice(deviceId!); selectedDevices.forEach(d => disconnectDevice(d.id));
router.back(); router.back();
}} }}
> >
@ -178,15 +201,28 @@ export default function SetupWiFiScreen() {
<Ionicons name="water" size={32} color={AppColors.primary} /> <Ionicons name="water" size={32} color={AppColors.primary} />
</View> </View>
<View style={styles.deviceInfo}> <View style={styles.deviceInfo}>
<Text style={styles.deviceName}>{deviceName}</Text> {selectedDevices.length === 1 ? (
<Text style={styles.deviceMeta}>Well ID: {wellId}</Text> <>
<Text style={styles.deviceName}>{deviceName}</Text>
<Text style={styles.deviceMeta}>Well ID: {wellId}</Text>
</>
) : (
<>
<Text style={styles.deviceName}>{selectedDevices.length} Sensors Selected</Text>
<Text style={styles.deviceMeta}>
{selectedDevices.map(d => d.name).join(', ')}
</Text>
</>
)}
</View> </View>
</View> </View>
{/* Instructions */} {/* Instructions */}
<View style={styles.instructionsCard}> <View style={styles.instructionsCard}>
<Text style={styles.instructionsText}> <Text style={styles.instructionsText}>
Select the WiFi network your sensor should connect to. Make sure the network has internet access. {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.`}
</Text> </Text>
</View> </View>