Add device settings screen
Features: - Device metadata display (name, MAC, location, description) - Edit device name and description - Update WiFi credentials (reconnect flow) - Remove device from beneficiary - Device history and diagnostics UI: - Clean settings form with validation - Delete confirmation dialog - Success/error feedback - Navigation back to equipment list on changes Route: /(tabs)/beneficiaries/[id]/device-settings/[deviceId]
This commit is contained in:
parent
3c3283e424
commit
5092678430
728
app/(tabs)/beneficiaries/[id]/device-settings/[deviceId].tsx
Normal file
728
app/(tabs)/beneficiaries/[id]/device-settings/[deviceId].tsx
Normal file
@ -0,0 +1,728 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
ScrollView,
|
||||||
|
TouchableOpacity,
|
||||||
|
Alert,
|
||||||
|
ActivityIndicator,
|
||||||
|
} 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' | 'offline';
|
||||||
|
lastSeen: Date;
|
||||||
|
beneficiaryId: string;
|
||||||
|
deploymentId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DeviceSettingsScreen() {
|
||||||
|
const { id, deviceId } = useLocalSearchParams<{ id: string; deviceId: string }>();
|
||||||
|
const {
|
||||||
|
connectedDevices,
|
||||||
|
isBLEAvailable,
|
||||||
|
connectDevice,
|
||||||
|
disconnectDevice,
|
||||||
|
getCurrentWiFi,
|
||||||
|
rebootDevice,
|
||||||
|
} = useBLE();
|
||||||
|
|
||||||
|
const [sensorInfo, setSensorInfo] = useState<SensorInfo | null>(null);
|
||||||
|
const [currentWiFi, setCurrentWiFi] = useState<WiFiStatus | null>(null);
|
||||||
|
const [isLoadingInfo, setIsLoadingInfo] = useState(true);
|
||||||
|
const [isConnecting, setIsConnecting] = useState(false);
|
||||||
|
const [isLoadingWiFi, setIsLoadingWiFi] = useState(false);
|
||||||
|
const [isRebooting, setIsRebooting] = useState(false);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
throw new Error('Failed to load sensor info');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sensor = response.data.find((s: any) => s.deviceId === deviceId);
|
||||||
|
|
||||||
|
if (sensor) {
|
||||||
|
setSensorInfo(sensor);
|
||||||
|
} 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 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 (
|
||||||
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
|
||||||
|
<Ionicons name="arrow-back" size={24} color={AppColors.textPrimary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.headerTitle}>Sensor Settings</Text>
|
||||||
|
<View style={styles.placeholder} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
<ActivityIndicator size="large" color={AppColors.primary} />
|
||||||
|
<Text style={styles.loadingText}>Loading sensor info...</Text>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
|
||||||
|
<Ionicons name="arrow-back" size={24} color={AppColors.textPrimary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.headerTitle}>Sensor Settings</Text>
|
||||||
|
<View style={styles.placeholder} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Simulator Warning */}
|
||||||
|
{!isBLEAvailable && (
|
||||||
|
<View style={styles.simulatorWarning}>
|
||||||
|
<Ionicons name="information-circle" size={18} color={AppColors.warning} />
|
||||||
|
<Text style={styles.simulatorWarningText}>
|
||||||
|
Running in Simulator - BLE features use mock data
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ScrollView style={styles.content} contentContainerStyle={styles.scrollContent}>
|
||||||
|
{/* Sensor Info Card */}
|
||||||
|
<View style={styles.sensorCard}>
|
||||||
|
<View style={styles.sensorIcon}>
|
||||||
|
<Ionicons name="water" size={48} color={AppColors.primary} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.sensorInfo}>
|
||||||
|
<Text style={styles.sensorName}>{sensorInfo.name}</Text>
|
||||||
|
<View style={styles.statusRow}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.statusDot,
|
||||||
|
{
|
||||||
|
backgroundColor:
|
||||||
|
sensorInfo.status === 'online' ? AppColors.success : AppColors.error,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Text style={styles.statusText}>
|
||||||
|
{sensorInfo.status === 'online' ? 'Online' : 'Offline'}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.statusSeparator}>•</Text>
|
||||||
|
<Text style={styles.lastSeenText}>{formatLastSeen(sensorInfo.lastSeen)}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Details Section */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Device Information</Text>
|
||||||
|
<View style={styles.detailsCard}>
|
||||||
|
<View style={styles.detailRow}>
|
||||||
|
<Text style={styles.detailLabel}>Well ID</Text>
|
||||||
|
<Text style={styles.detailValue}>{sensorInfo.wellId}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.detailDivider} />
|
||||||
|
<View style={styles.detailRow}>
|
||||||
|
<Text style={styles.detailLabel}>MAC Address</Text>
|
||||||
|
<Text style={styles.detailValue}>{sensorInfo.mac}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.detailDivider} />
|
||||||
|
<View style={styles.detailRow}>
|
||||||
|
<Text style={styles.detailLabel}>Deployment ID</Text>
|
||||||
|
<Text style={styles.detailValue}>{sensorInfo.deploymentId}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* BLE Connection Section */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Bluetooth Connection</Text>
|
||||||
|
{isConnected ? (
|
||||||
|
<View style={styles.connectedCard}>
|
||||||
|
<View style={styles.connectedHeader}>
|
||||||
|
<Ionicons name="bluetooth" size={24} color={AppColors.success} />
|
||||||
|
<Text style={styles.connectedText}>Connected</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.disconnectButton}
|
||||||
|
onPress={() => disconnectDevice(deviceId!)}
|
||||||
|
>
|
||||||
|
<Text style={styles.disconnectButtonText}>Disconnect</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.connectButton}
|
||||||
|
onPress={handleConnect}
|
||||||
|
disabled={isConnecting}
|
||||||
|
>
|
||||||
|
{isConnecting ? (
|
||||||
|
<ActivityIndicator size="small" color={AppColors.white} />
|
||||||
|
) : (
|
||||||
|
<Ionicons name="bluetooth" size={20} color={AppColors.white} />
|
||||||
|
)}
|
||||||
|
<Text style={styles.connectButtonText}>
|
||||||
|
{isConnecting ? 'Connecting...' : 'Connect via Bluetooth'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* WiFi Status Section */}
|
||||||
|
{isConnected && (
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>WiFi Status</Text>
|
||||||
|
{isLoadingWiFi ? (
|
||||||
|
<View style={styles.loadingWiFiCard}>
|
||||||
|
<ActivityIndicator size="small" color={AppColors.primary} />
|
||||||
|
<Text style={styles.loadingWiFiText}>Loading WiFi status...</Text>
|
||||||
|
</View>
|
||||||
|
) : currentWiFi ? (
|
||||||
|
<View style={styles.wifiCard}>
|
||||||
|
<View style={styles.wifiHeader}>
|
||||||
|
<Ionicons name="wifi" size={24} color={AppColors.success} />
|
||||||
|
<View style={styles.wifiInfo}>
|
||||||
|
<Text style={styles.wifiSSID}>{currentWiFi.ssid}</Text>
|
||||||
|
<Text
|
||||||
|
style={[styles.wifiSignal, { color: getSignalColor(currentWiFi.rssi) }]}
|
||||||
|
>
|
||||||
|
{getSignalStrength(currentWiFi.rssi)} ({currentWiFi.rssi} dBm)
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity style={styles.changeWiFiButton} onPress={handleChangeWiFi}>
|
||||||
|
<Ionicons name="settings-outline" size={18} color={AppColors.primary} />
|
||||||
|
<Text style={styles.changeWiFiText}>Change WiFi</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View style={styles.noWiFiCard}>
|
||||||
|
<Ionicons name="wifi-outline" size={32} color={AppColors.textMuted} />
|
||||||
|
<Text style={styles.noWiFiText}>Not connected to WiFi</Text>
|
||||||
|
<TouchableOpacity style={styles.setupWiFiButton} onPress={handleChangeWiFi}>
|
||||||
|
<Text style={styles.setupWiFiText}>Setup WiFi</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Actions Section */}
|
||||||
|
{isConnected && (
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Actions</Text>
|
||||||
|
<View style={styles.actionsCard}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.actionButton}
|
||||||
|
onPress={loadWiFiStatus}
|
||||||
|
disabled={isLoadingWiFi}
|
||||||
|
>
|
||||||
|
<Ionicons name="refresh" size={20} color={AppColors.primary} />
|
||||||
|
<Text style={styles.actionButtonText}>Refresh WiFi Status</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={styles.actionDivider} />
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.actionButton}
|
||||||
|
onPress={handleReboot}
|
||||||
|
disabled={isRebooting}
|
||||||
|
>
|
||||||
|
{isRebooting ? (
|
||||||
|
<ActivityIndicator size="small" color={AppColors.warning} />
|
||||||
|
) : (
|
||||||
|
<Ionicons name="power" size={20} color={AppColors.warning} />
|
||||||
|
)}
|
||||||
|
<Text style={[styles.actionButtonText, { color: AppColors.warning }]}>
|
||||||
|
Reboot Sensor
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Info Card */}
|
||||||
|
<View style={styles.infoCard}>
|
||||||
|
<View style={styles.infoHeader}>
|
||||||
|
<Ionicons name="information-circle" size={20} color={AppColors.info} />
|
||||||
|
<Text style={styles.infoTitle}>About Settings</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
• 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
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
// 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user