1import { View } from "react-native";
2
3import type { RemoteClientConnectionState } from "../../lib/connection";
4import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
5import { themeColorWithAlpha, type MobileThemeVariables } from "../../lib/mobileTheme";
6
7export type ConnectionStatusDotState = RemoteClientConnectionState;
8
9function statusDotTone(
10 state: ConnectionStatusDotState,
11 theme: MobileThemeVariables,
12 dark: boolean,
13): {
14 readonly dotColor: string;
15 readonly haloColor: string;
16} {
17 switch (state) {
18 case "available":
19 case "unsupported":
20 return {
21 dotColor: theme["--color-icon-muted"],
22 haloColor: themeColorWithAlpha(theme["--color-icon-muted"], 0.42),
23 };
24 case "connected":
25 return {
26 dotColor: dark ? "#34d399" : "#059669",
27 haloColor: themeColorWithAlpha(dark ? "#34d399" : "#059669", 0.48),
28 };
29 case "connecting":
30 case "reconnecting":
31 return {
32 dotColor: theme["--color-warning-foreground"],
33 haloColor: themeColorWithAlpha(theme["--color-warning-foreground"], 0.5),
34 };
35 case "offline":
36 case "error":
37 return {
38 dotColor: theme["--color-danger-foreground"],
39 haloColor: themeColorWithAlpha(theme["--color-danger-foreground"], 0.48),
40 };
41 }
42}
43
44export function ConnectionStatusDot(props: {
45 readonly state: ConnectionStatusDotState;
46 readonly pulse: boolean;
47 readonly size?: number;
48}) {
49 const { themeAppearance, themeVariables } = useAppearancePreferences();
50 const tone = statusDotTone(props.state, themeVariables, themeAppearance === "dark");
51 const dotSize = props.size ?? 10;
52 const haloSize = dotSize + 4;
53 const containerSize = haloSize + 4;
54
55 return (
56 <View
57 style={{
58 width: containerSize,
59 height: containerSize,
60 alignItems: "center",
61 justifyContent: "center",
62 }}
63 >
64 {/* Looping Reanimated updates can starve React commits while commit pausing is disabled. */}
65 {props.pulse ? (
66 <View
67 style={{
68 position: "absolute",
69 width: haloSize,
70 height: haloSize,
71 borderRadius: haloSize / 2,
72 backgroundColor: tone.haloColor,
73 opacity: 0.3,
74 }}
75 />
76 ) : null}
77 <View
78 style={{
79 width: dotSize,
80 height: dotSize,
81 borderRadius: dotSize / 2,
82 backgroundColor: tone.dotColor,
83 }}
84 />
85 </View>
86 );
87}