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,
} = useBLE();
const [selectedDevice, setSelectedDevice] = useState<WPDevice | null>(null);
const [selectedDevices, setSelectedDevices] = useState<Set<string>>(new Set());
const [isConnecting, setIsConnecting] = useState(false);
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 () => {
try {
await scanDevices();
@ -51,36 +80,26 @@ export default function AddSensorScreen() {
}
};
const handleConnect = async (device: WPDevice) => {
setIsConnecting(true);
setSelectedDevice(device);
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 handleAddSelected = () => {
if (selectedCount === 0) {
Alert.alert('No Sensors Selected', 'Please select at least one sensor to add.');
return;
}
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) => {
@ -172,28 +191,52 @@ export default function AddSensorScreen() {
<>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>Found Sensors ({foundDevices.length})</Text>
<TouchableOpacity style={styles.rescanButton} onPress={handleScan}>
<Ionicons name="refresh" size={18} color={AppColors.primary} />
<Text style={styles.rescanText}>Rescan</Text>
</TouchableOpacity>
<View style={styles.sectionActions}>
<TouchableOpacity style={styles.selectAllButton} onPress={toggleSelectAll}>
<Ionicons
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 style={styles.devicesList}>
{foundDevices.map((device) => {
const isConnected = connectedDevices.has(device.id);
const isConnectingThis = isConnecting && selectedDevice?.id === device.id;
const isSelected = selectedDevices.has(device.id);
return (
<TouchableOpacity
key={device.id}
style={[
styles.deviceCard,
isSelected && styles.deviceCardSelected,
isConnected && styles.deviceCardConnected,
]}
onPress={() => handleConnect(device)}
disabled={isConnectingThis || isConnected}
onPress={() => toggleDeviceSelection(device.id)}
disabled={isConnected}
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.deviceIcon}>
<Ionicons name="water" size={24} color={AppColors.primary} />
@ -216,19 +259,30 @@ export default function AddSensorScreen() {
</View>
</View>
{isConnectingThis ? (
<ActivityIndicator size="small" color={AppColors.primary} />
) : isConnected ? (
<View style={styles.connectedBadge}>
<Ionicons name="checkmark-circle" size={20} color={AppColors.success} />
{isConnected && (
<View style={styles.alreadyAddedBadge}>
<Text style={styles.alreadyAddedText}>Added</Text>
</View>
) : (
<Ionicons name="chevron-forward" size={20} color={AppColors.textMuted} />
)}
</TouchableOpacity>
);
})}
</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',
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: {
flexDirection: 'row',
alignItems: 'center',
@ -427,12 +496,37 @@ const styles = StyleSheet.create({
padding: Spacing.md,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
...Shadows.xs,
},
deviceCardSelected: {
borderWidth: 2,
borderColor: AppColors.primary,
},
deviceCardConnected: {
borderWidth: 2,
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: {
flex: 1,
@ -471,8 +565,37 @@ const styles = StyleSheet.create({
fontSize: FontSizes.xs,
fontWeight: FontWeights.medium,
},
connectedBadge: {
padding: Spacing.xs,
alreadyAddedBadge: {
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
emptyState: {

View File

@ -25,16 +25,39 @@ import {
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, deviceId, deviceName, wellId } = useLocalSearchParams<{
const { id, devices: devicesParam } = useLocalSearchParams<{
id: string;
deviceId: string;
deviceName: string;
wellId: 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<WiFiNetwork[]>([]);
const [isLoadingNetworks, setIsLoadingNetworks] = useState(false);
const [selectedNetwork, setSelectedNetwork] = useState<WiFiNetwork | null>(null);
@ -160,8 +183,8 @@ export default function SetupWiFiScreen() {
<TouchableOpacity
style={styles.backButton}
onPress={() => {
// Disconnect BLE before going back
disconnectDevice(deviceId!);
// Disconnect all BLE devices before going back
selectedDevices.forEach(d => disconnectDevice(d.id));
router.back();
}}
>
@ -178,15 +201,28 @@ export default function SetupWiFiScreen() {
<Ionicons name="water" size={32} color={AppColors.primary} />
</View>
<View style={styles.deviceInfo}>
<Text style={styles.deviceName}>{deviceName}</Text>
<Text style={styles.deviceMeta}>Well ID: {wellId}</Text>
{selectedDevices.length === 1 ? (
<>
<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>
{/* Instructions */}
<View style={styles.instructionsCard}>
<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>
</View>