import { useState } from 'react'; import { View, Text } from 'react-native'; import { Image } from 'expo-image'; import { useOnlineUsers } from '../hooks/useOnlineUsers'; import { resolveAvatar } from '../lib/resolveAvatar'; import { useColors } from '../lib/theme'; type Size = 'sm' | 'md' | 'lg' | 'xl'; type Props = { userId: string | null; avatar: string | null; nickname: string; size?: Size; showOnlineIndicator?: boolean; isBot?: boolean; }; const SIZE_MAP: Record< Size, { avatar: number; dot: number; border: number; font: number; inset: number } > = { // inset = bottom/right Offset, berechnet via `avatarRadius*0.293 - dotRadius` // damit der Dot-Center exakt auf der Avatar-Perimeter bei 45° sitzt (4:30 // clock position). Konsistente Insta-Optik unabhängig vom Avatar-Size. sm: { avatar: 28, dot: 8, border: 2, font: 11, inset: 0 }, md: { avatar: 40, dot: 11, border: 2.5, font: 14, inset: 0 }, lg: { avatar: 56, dot: 14, border: 3, font: 18, inset: 1 }, xl: { avatar: 96, dot: 18, border: 3, font: 32, inset: 5 }, }; function OnlineDot({ size, bgColor }: { size: Size; bgColor: string }) { const s = SIZE_MAP[size]; return ( ); } export function UserAvatar({ userId, avatar, nickname, size = 'md', showOnlineIndicator = true, isBot = false, }: Props) { const colors = useColors(); const { isOnline } = useOnlineUsers(); const [imageFailed, setImageFailed] = useState(false); const s = SIZE_MAP[size]; const radius = s.avatar / 2; const hasImage = !!avatar && !isBot && !imageFailed; const avatarUrl = hasImage ? resolveAvatar(avatar, nickname) : ''; const initials = (nickname.charAt(0) + (nickname.charAt(1) ?? '')).toUpperCase() || '?'; const showDot = showOnlineIndicator !== false && !!userId && !isBot && isOnline(userId); return ( {hasImage ? ( setImageFailed(true)} style={{ width: s.avatar, height: s.avatar, borderRadius: radius, backgroundColor: colors.surfaceElevated, }} contentFit="cover" /> ) : ( {initials} )} {showDot && } ); }