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:
parent
52def3cb79
commit
b738d86419
@ -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: {
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user