Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 0s
When the app returns from background, check if the WebSocket is still open. If not, close the stale socket and reconnect with reset backoff. Proactively close the WS when backgrounding to save battery and avoid OS killing it mid-frame. Add "reconnecting" connection status with amber pulsing badge so users see the app is re-establishing connection. Fixes #33 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
71 lines
1.9 KiB
TypeScript
71 lines
1.9 KiB
TypeScript
import React from "react";
|
|
import { Animated, StyleSheet, Text, View } from "react-native";
|
|
import { useEffect, useRef } from "react";
|
|
|
|
import { Colors } from "@/constants/colors";
|
|
import type { ConnectionStatus } from "@/context/TimmyContext";
|
|
|
|
const C = Colors.dark;
|
|
|
|
const STATUS_CONFIG: Record<ConnectionStatus, { color: string; label: string }> = {
|
|
connecting: { color: "#F59E0B", label: "Connecting" },
|
|
connected: { color: "#10B981", label: "Live" },
|
|
disconnected: { color: "#6B7280", label: "Offline" },
|
|
reconnecting: { color: "#F59E0B", label: "Reconnecting" },
|
|
error: { color: "#EF4444", label: "Error" },
|
|
};
|
|
|
|
export function ConnectionBadge({ status }: { status: ConnectionStatus }) {
|
|
const pulseAnim = useRef(new Animated.Value(1)).current;
|
|
|
|
useEffect(() => {
|
|
if (status === "connecting" || status === "reconnecting") {
|
|
const pulse = Animated.loop(
|
|
Animated.sequence([
|
|
Animated.timing(pulseAnim, { toValue: 0.3, duration: 600, useNativeDriver: true }),
|
|
Animated.timing(pulseAnim, { toValue: 1, duration: 600, useNativeDriver: true }),
|
|
])
|
|
);
|
|
pulse.start();
|
|
return () => pulse.stop();
|
|
} else {
|
|
pulseAnim.setValue(1);
|
|
}
|
|
}, [status]);
|
|
|
|
const config = STATUS_CONFIG[status];
|
|
|
|
return (
|
|
<View style={styles.badge}>
|
|
<Animated.View
|
|
style={[styles.dot, { backgroundColor: config.color, opacity: pulseAnim }]}
|
|
/>
|
|
<Text style={[styles.label, { color: config.color }]}>{config.label}</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
badge: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: 5,
|
|
backgroundColor: C.surface,
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 4,
|
|
borderRadius: 20,
|
|
borderWidth: 1,
|
|
borderColor: C.border,
|
|
},
|
|
dot: {
|
|
width: 6,
|
|
height: 6,
|
|
borderRadius: 3,
|
|
},
|
|
label: {
|
|
fontSize: 11,
|
|
fontFamily: "Inter_500Medium",
|
|
letterSpacing: 0.5,
|
|
},
|
|
});
|