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,
|
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: {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user