import { ContextMenu, Host, Button as SwiftUIButton } from "@expo/ui/swift-ui"; import { Ionicons } from "@expo/vector-icons"; import { SymbolView } from "expo-symbols"; import React, { useCallback, useEffect, useState } from "react"; import { ActivityIndicator, Alert, AppState, FlatList, RefreshControl, StyleSheet, Text, TouchableOpacity, View, } from "react-native"; import { useColorScheme } from "../../hooks/use-color-scheme"; import api from "../../src/services/api"; import { Device } from "../../src/types"; import * as Burnt from "burnt"; export default function DeviceListScreen() { const colorScheme = useColorScheme() ?? "light"; const isDark = colorScheme === "dark"; const bgColor = isDark ? "#0b0b0d" : "#f5f5f5"; const cardBg = isDark ? "#1c1c1e" : "#fff"; const textColor = isDark ? "#ffffff" : "#333333"; const subTextColor = isDark ? "#c6c6c8" : "#666666"; const activityColor = isDark ? "#0A84FF" : "#007AFF"; const [devices, setDevices] = useState([]); const [isLoading, setIsLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const fetchDevices = useCallback(async (showLoading = false) => { try { if (showLoading) setIsLoading(true); const data = await api.getDevices(); setDevices(data); } catch (error: any) { // For background/periodic refreshes, avoid interruptive alerts if (showLoading) { Alert.alert("Error", error.message || "Failed to load devices"); } } finally { if (showLoading) setIsLoading(false); } }, []); useEffect(() => { fetchDevices(true); }, [fetchDevices]); useEffect(() => { let intervalId: number | null = null; const startPolling = () => { if (intervalId !== null) return; intervalId = setInterval(() => { fetchDevices(false); }, 10000) as unknown as number; }; const stopPolling = () => { if (intervalId !== null) { clearInterval(intervalId); intervalId = null; } }; // Start polling while app is active; pause when backgrounded startPolling(); const onAppStateChange = (nextAppState: string) => { if (nextAppState === "active") { startPolling(); } else { stopPolling(); } }; const subscription = AppState.addEventListener("change", onAppStateChange); return () => { stopPolling(); subscription.remove(); }; }, [fetchDevices]); const onRefresh = async () => { setRefreshing(true); await fetchDevices(false); setRefreshing(false); }; const handleWake = async (device: Device) => { try { await api.wakeDevice(device.id); Burnt.toast({ title: "Success", preset: "done", message: `Waking ${device.name} up.`, }); } catch (error: any) { Burnt.toast({ title: "Error", preset: "error", message: error.message || `Failed to wake up ${device.name}.`, }); } }; const handleSleep = async (device: Device) => { Alert.alert("Confirm", `Send ${device.name} to sleep?`, [ { text: "Cancel", style: "cancel" }, { text: "Sleep", style: "destructive", onPress: async () => { try { await api.sleepDevice(device.id); Burnt.toast({ title: "Success", preset: "done", message: `Sending ${device.name} to sleep.`, }); } catch (error: any) { Burnt.toast({ title: "Error", preset: "error", message: error.message || `Failed to send ${device.name} to sleep.`, }); } }, }, ]); }; const handleReboot = async (device: Device) => { Alert.alert("Confirm", `Reboot ${device.name}?`, [ { text: "Cancel", style: "cancel" }, { text: "Reboot", style: "destructive", onPress: async () => { try { await api.rebootDevice(device.id); Burnt.toast({ title: "Success", preset: "done", message: `Rebooting ${device.name}.`, }); } catch (error: any) { Burnt.toast({ title: "Error", preset: "error", message: error.message || `Failed to reboot ${device.name}`, }); } }, }, ]); }; const handleShutdown = async (device: Device) => { Alert.alert( "Confirm Shutdown", `Shutdown ${device.name}? This cannot be undone.`, [ { text: "Cancel", style: "cancel" }, { text: "Shutdown", style: "destructive", onPress: async () => { try { await api.shutdownDevice(device.id); Burnt.toast({ title: "Success", preset: "done", message: `Shutting down ${device.name}.`, }); } catch (error: any) { Burnt.toast({ title: "Error", preset: "error", message: error.message || `Failed to shut down ${device.name}.`, }); } }, }, ] ); }; const handleDelete = (device: Device) => { Alert.alert("Delete Device", `Delete "${device.name}"?`, [ { text: "Cancel", style: "cancel", onPress: () => { // Close alert }, }, { text: "Delete", style: "destructive", onPress: async () => { try { await api.deleteDevice(device.id); Burnt.toast({ title: "Success", preset: "done", message: `Deleted ${device.name} successfully.`, }); fetchDevices(false); } catch (error: any) { Burnt.toast({ title: "Error", preset: "error", message: error.message || `Failed to delete ${device.name}.`, }); } }, }, ]); }; const getStatusColor = (status: string) => { switch (status?.toLowerCase()) { case "online": return "#4CAF50"; case "offline": return "#f44336"; default: return "#ff9800"; } }; const ActionIcon = ({ name, symbolName, color, onPress, fallbackName, }: { name: string; symbolName: string; color: string; onPress: () => void; fallbackName?: string; }) => ( } style={styles.actionIcon} /> ); const renderDevice = ({ item }: { item: Device }) => ( handleDelete(item)} > Delete Device {item.name} {item.mac} } style={[ styles.statusSymbol, { shadowColor: getStatusColor(item.status), shadowOpacity: isDark ? 0.9 : 0.6, }, ]} /> handleWake(item)} /> handleSleep(item)} /> handleReboot(item)} /> handleShutdown(item)} /> ); if (isLoading) { return ( ); } return ( item.id} contentContainerStyle={styles.list} refreshControl={ } ListEmptyComponent={ No devices found } /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#f5f5f5", }, headerRight: { flexDirection: "row", gap: 16, }, headerButton: { paddingHorizontal: 8, }, loadingContainer: { flex: 1, justifyContent: "center", alignItems: "center", }, list: { padding: 15, gap: 15, }, deviceCard: { backgroundColor: "#fff", borderRadius: 12, padding: 15, paddingRight: 25, shadowColor: "#000", shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, deviceHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 12, }, deviceInfo: { flex: 1, }, deviceName: { fontSize: 18, fontWeight: "bold", color: "#333", marginBottom: 4, }, deviceIP: { fontSize: 14, color: "#666", }, statusDot: { width: 12, height: 12, borderRadius: 6, }, deviceActions: { flexDirection: "row", alignItems: "center", justifyContent: "flex-start", marginTop: 8, paddingBottom: 6, }, actionIconContainer: { padding: 6, marginRight: 12, justifyContent: "center", alignItems: "center", minWidth: 36, minHeight: 36, }, actionIcon: { width: 22, height: 22, }, actionButtonText: { color: "#fff", fontWeight: "600", fontSize: 12, }, wakeButton: { backgroundColor: "#4CAF50", }, sleepButton: { backgroundColor: "#FF9800", }, rebootButton: { backgroundColor: "#2196F3", }, shutdownButton: { backgroundColor: "#f44336", }, statusSymbol: { width: 18, height: 18, borderRadius: 9, shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0.6, shadowRadius: 8, elevation: 6, }, emptyContainer: { alignItems: "center", paddingVertical: 50, }, emptyText: { fontSize: 16, color: "#999", }, });