import { useEffect, useState, type ReactNode } from "react";
import { Pressable, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import * as Clipboard from "expo-clipboard";
import { Container } from "@/components/Container";
import { SafeAreaScrollView } from "@/components/safe-area-scroll-view";
import { Button } from "@/components/ui/button";
import { Text } from "@/components/ui/text";
import { Ble } from "@aris/ble";
import { useBleStore } from "@/store/ble";
import { cn } from "@/lib/utils";
import { Ionicons } from "@expo/vector-icons";
const formatTime = (timestamp: number | null) => {
if (!timestamp) {
return "Never";
}
return new Date(timestamp).toLocaleTimeString();
};
export default function BleScreen() {
const initialize = useBleStore((state) => state.initialize);
const isSupported = useBleStore((state) => state.isSupported);
const insets = useSafeAreaInsets();
useEffect(() => {
initialize();
}, [initialize]);
if (!isSupported) {
return (
BLE
Bluetooth peripheral status and controls.
BLE Unavailable
This page is currently supported on iOS only.
);
}
return (
BLE
Bluetooth peripheral status and controls.
);
}
function ValueRow({ label, value }: { label: ReactNode; value: ReactNode }) {
return (
{typeof label === "string" ? (
{label}
) : (
label
)}
{typeof value === "string" || typeof value === "number" ? (
{value}
) : (
value
)}
);
}
function BleStatusSection() {
const bluetoothState = useBleStore((state) => state.bluetoothState);
const isSubscribed = useBleStore((state) => state.isSubscribed);
const subscribedCount = useBleStore((state) => state.subscribedCount);
const connectionLabel = isSubscribed ? "Connected" : "Not Connected";
return (
Status
);
}
function ControlPanel() {
const advertisingEnabled = useBleStore((state) => state.advertisingEnabled);
const setAdvertisingEnabled = useBleStore(
(state) => state.setAdvertisingEnabled,
);
const wifiRequested = useBleStore((state) => state.wifiRequested);
const setWifiRequested = useBleStore((state) => state.setWifiRequested);
const sendFixtureFeedNow = useBleStore((state) => state.sendFixtureFeedNow);
const handleCopyUUIDs = async () => {
const uuids = Ble.getUuids();
const text = [
`SERVICE_UUID=${uuids.serviceUUID}`,
`FEED_TX_UUID=${uuids.feedTxUUID}`,
`CONTROL_RX_UUID=${uuids.controlRxUUID}`,
`WIFI_REQUEST_TX_UUID=${uuids.wifiRequestTxUUID}`,
].join("\n");
await Clipboard.setStringAsync(text);
};
return (
Control Panel
}
label="Advertising"
checked={advertisingEnabled}
onCheckChange={setAdvertisingEnabled}
/>
}
label="WiFi"
checked={wifiRequested}
onCheckChange={setWifiRequested}
/>
);
}
function ControlTile({
icon,
label,
checked,
onCheckChange,
}: {
icon: ReactNode;
label: ReactNode;
checked: boolean;
onCheckChange: (checked: boolean) => void;
}) {
const [isPressed, setIsPressed] = useState(false);
return (
{
setIsPressed(true);
}}
onPressOut={() => {
setIsPressed(false);
}}
onPress={() => {
onCheckChange(!checked);
}}
className={cn(
"flex-1 items-start justify-center rounded-md px-3 py-2 gap-1",
checked ? "border-2 border-primary" : "border border-border",
{ "bg-accent": isPressed },
)}
>
{icon}
{label}
);
}
function TelemetrySection() {
const lastMsgIdSent = useBleStore((state) => state.lastMsgIdSent);
const lastPingAt = useBleStore((state) => state.lastPingAt);
const lastDataAt = useBleStore((state) => state.lastDataAt);
const lastNotifyAt = useBleStore((state) => state.lastNotifyAt);
const notifyQueueDepth = useBleStore((state) => state.notifyQueueDepth);
const droppedNotifyPackets = useBleStore(
(state) => state.droppedNotifyPackets,
);
const lastCommand = useBleStore((state) => state.lastCommand);
return (
Telemetry
{droppedNotifyPackets > 0 ? (
) : null}
{lastCommand ? (
) : null}
);
}
function UuidsSection() {
const uuids = Ble.getUuids();
return (
UUIDs
{`SERVICE_UUID=${uuids.serviceUUID}\nFEED_TX_UUID=${uuids.feedTxUUID}\nCONTROL_RX_UUID=${uuids.controlRxUUID}\nWIFI_REQUEST_TX_UUID=${uuids.wifiRequestTxUUID}`}
);
}