fix(native): zwei Circles + animierter Gesamt-Verteilungs-Balken drunter
Statt Half-Donut (Höhen-Mismatch mit Circles): zwei volle Circles (Mobil/Computer) + darunter ein eigener animierter Balken (grün/blau-Segmente, gleiche Easing/Dauer wie die Ringe) mit Legende. Kein native-Default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c3478f4743
commit
ca72437f18
@ -33,6 +33,7 @@ import { useUserPlan } from '../hooks/useUserPlan';
|
||||
import { AppHeader } from '../components/AppHeader';
|
||||
import { MagicSheet } from '../components/devices/MagicSheet';
|
||||
import { DeviceSlotDonut, MOBILE_COLOR, DESKTOP_COLOR } from '../components/devices/DeviceSlotDonut';
|
||||
import { DeviceDistributionBar } from '../components/devices/DeviceDistributionBar';
|
||||
import { DeviceStatusPill } from '../components/devices/DeviceStatusPill';
|
||||
import { DeviceDetailSheet, type DeviceDetail } from '../components/devices/DeviceDetailSheet';
|
||||
import { deviceImage } from '../components/devices/deviceIcon';
|
||||
@ -567,9 +568,9 @@ export default function DevicesScreen() {
|
||||
}}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* Slot-Ringe: Mobil · Gesamt (Verteilung) · Computer */}
|
||||
<View style={{ marginBottom: -4 }}>
|
||||
<View style={{ flexDirection: 'row', gap: 8, marginTop: 4 }}>
|
||||
{/* Zwei Circles (Mobil/Computer) + animierter Gesamt-Verteilungs-Balken */}
|
||||
<View style={{ gap: 18, marginBottom: -4 }}>
|
||||
<View style={{ flexDirection: 'row', gap: 12, marginTop: 4 }}>
|
||||
<DeviceSlotDonut
|
||||
segments={[{ value: mobileCount, color: MOBILE_COLOR }]}
|
||||
total={mobileLimit}
|
||||
@ -582,17 +583,12 @@ export default function DevicesScreen() {
|
||||
atLimit={atDesktopLimit}
|
||||
label={t('devices.progress_desktop')}
|
||||
/>
|
||||
<DeviceSlotDonut
|
||||
half
|
||||
segments={[
|
||||
{ value: mobileCount, color: MOBILE_COLOR },
|
||||
{ value: desktopCount, color: DESKTOP_COLOR },
|
||||
]}
|
||||
total={mobileLimit + desktopLimit}
|
||||
atLimit={mobileCount + desktopCount >= mobileLimit + desktopLimit}
|
||||
label={t('devices.progress_total')}
|
||||
/>
|
||||
</View>
|
||||
<DeviceDistributionBar
|
||||
mobile={mobileCount}
|
||||
desktop={desktopCount}
|
||||
total={mobileLimit + desktopLimit}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Unified devices section: Mobile zuerst, dann Desktop */}
|
||||
|
||||
108
apps/rebreak-native/components/devices/DeviceDistributionBar.tsx
Normal file
108
apps/rebreak-native/components/devices/DeviceDistributionBar.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { Animated, Easing, Text, View } from 'react-native';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useColors } from '../../lib/theme';
|
||||
import { MOBILE_COLOR, DESKTOP_COLOR } from './DeviceSlotDonut';
|
||||
|
||||
/**
|
||||
* Animierter Verteilungs-Balken (Gesamt) — gleiche Design-Sprache wie die
|
||||
* Slot-Ringe (grün=Mobil, blau=Computer, gleiche Easing/Dauer). Sitzt unter
|
||||
* den beiden Circles und zeigt die Aufteilung mobil/stationär.
|
||||
*/
|
||||
export function DeviceDistributionBar({
|
||||
mobile,
|
||||
desktop,
|
||||
total,
|
||||
}: {
|
||||
mobile: number;
|
||||
desktop: number;
|
||||
total: number;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const colors = useColors();
|
||||
const safeTotal = Math.max(1, total);
|
||||
const used = mobile + desktop;
|
||||
|
||||
const anim = useRef(new Animated.Value(0)).current;
|
||||
useEffect(() => {
|
||||
anim.setValue(0);
|
||||
Animated.timing(anim, {
|
||||
toValue: 1,
|
||||
duration: 1400,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
useNativeDriver: false,
|
||||
}).start();
|
||||
}, [mobile, desktop, total, anim]);
|
||||
|
||||
const mobileW = anim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['0%', `${(mobile / safeTotal) * 100}%`],
|
||||
});
|
||||
const desktopW = anim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['0%', `${(desktop / safeTotal) * 100}%`],
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={{ gap: 8 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 11,
|
||||
color: colors.textMuted,
|
||||
fontFamily: 'Nunito_700Bold',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.8,
|
||||
}}
|
||||
>
|
||||
{t('devices.progress_total')}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 14, color: colors.text, fontFamily: 'Nunito_900Black', letterSpacing: -0.3 }}>
|
||||
{used}
|
||||
<Text style={{ fontSize: 12, color: colors.textMuted, fontFamily: 'Nunito_700Bold' }}>
|
||||
/{total}
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Animated.View style={{ width: mobileW, height: '100%', backgroundColor: MOBILE_COLOR }} />
|
||||
<Animated.View style={{ width: desktopW, height: '100%', backgroundColor: DESKTOP_COLOR }} />
|
||||
</View>
|
||||
|
||||
<View style={{ flexDirection: 'row', gap: 16 }}>
|
||||
<Legend color={MOBILE_COLOR} label={t('devices.progress_mobile')} count={mobile} colors={colors} />
|
||||
<Legend color={DESKTOP_COLOR} label={t('devices.progress_desktop')} count={desktop} colors={colors} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function Legend({
|
||||
color,
|
||||
label,
|
||||
count,
|
||||
colors,
|
||||
}: {
|
||||
color: string;
|
||||
label: string;
|
||||
count: number;
|
||||
colors: ReturnType<typeof useColors>;
|
||||
}) {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
||||
<View style={{ width: 8, height: 8, borderRadius: 4, backgroundColor: color }} />
|
||||
<Text style={{ fontSize: 12, color: colors.textMuted, fontFamily: 'Nunito_600SemiBold' }}>
|
||||
{label} {count}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user