import { useEffect, useRef, useState } from 'react'; import { Animated, Easing, Text, View } from 'react-native'; import Svg, { Circle, Path } from 'react-native-svg'; import { useColors } from '../../lib/theme'; const SIZE = 88; const STROKE = 11; const R = (SIZE - STROKE) / 2; /** Geräte-Kategorie-Farben — auch im Gesamt-Half-Donut (Verteilung). */ export const MOBILE_COLOR = '#22c55e'; export const DESKTOP_COLOR = '#2563eb'; export type SlotSegment = { value: number; color: string }; function polar(cx: number, cy: number, r: number, deg: number) { const rad = (deg * Math.PI) / 180; return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) }; } function arcPath(cx: number, cy: number, r: number, startDeg: number, endDeg: number) { const start = polar(cx, cy, r, startDeg); const end = polar(cx, cy, r, endDeg); const large = endDeg - startDeg > 180 ? 1 : 0; return `M ${start.x} ${start.y} A ${r} ${r} 0 ${large} 1 ${end.x} ${end.y}`; } /** * Slot-Anzeige (react-native-svg, keine Lib), gleiche Größe in beiden Modi: * - `half=false` (Default): voller Progress-Ring — eine Kategorie (Mobil/Computer). * - `half=true`: Half-Donut für die Gesamt-Verteilung (Mobil-/Computer-Anteil * als zwei farbige Bögen). */ export function DeviceSlotDonut({ segments, total, label, atLimit, half = false, }: { segments: SlotSegment[]; total: number; label: string; atLimit: boolean; half?: boolean; }) { const colors = useColors(); const cx = SIZE / 2; // Half-Donut tiefer setzen, damit der Bogen mittig im SIZE×SIZE-Feld sitzt. const cy = half ? SIZE / 2 + R / 2 : SIZE / 2; const r = R; const safeTotal = Math.max(1, total); const used = segments.reduce((s, x) => s + x.value, 0); const startAngle = half ? 180 : -90; const sweep = half ? 180 : 360; const anim = useRef(new Animated.Value(0)).current; const [progress, setProgress] = useState(0); useEffect(() => { anim.setValue(0); const l = anim.addListener(({ value }) => setProgress(value)); Animated.timing(anim, { toValue: 1, duration: 1400, easing: Easing.out(Easing.cubic), useNativeDriver: false, }).start(); return () => anim.removeListener(l); }, [used, total, anim]); let cum = startAngle; const arcs = segments.map((seg) => { const span = sweep * (seg.value / safeTotal); const start = cum; const end = cum + span; cum = end; return { start, end, color: seg.color }; }); return ( {half ? ( ) : ( )} {arcs.map((a, i) => { const animEnd = a.start + (a.end - a.start) * progress; if (animEnd <= a.start + 0.5) return null; const drawEnd = Math.min(animEnd, a.start + (half ? 179.99 : 359.99)); return ( ); })} {used} /{total} {label} ); }