/* * 공통 Avatar — 프로필 사진(photoUrl) 표시, 실패/부재 시 이름 이니셜 폴백. WISE 모바일 Avatar 이식. * 레퍼런스: guardia-messenger/app/uiws/_lib/components/Avatar.tsx. * 사진 서빙 엔드포인트는 인증 필요 — 는 api 인터셉터를 거치지 않으므로 Bearer 헤더를 직접 부여. * 색은 theme 토큰만 사용(신규 토큰 발명 금지). */ import React, { useEffect, useState } from 'react'; import { Image, StyleSheet, Text, View } from 'react-native'; import { getAccessToken } from '../lib/api'; import { colors } from '../theme'; interface AvatarProps { photoUrl?: string | null; name?: string | null; size?: number; testID?: string; } function initials(name?: string | null): string { if (!name) return '?'; const trimmed = name.trim(); if (!trimmed) return '?'; const isAscii = /^[\x00-\x7F]+$/.test(trimmed); if (isAscii) { const parts = trimmed.split(/\s+/); return (parts.length >= 2 ? parts[0][0] + parts[1][0] : trimmed.slice(0, 2)).toUpperCase(); } return trimmed.slice(0, 1); } export function Avatar({ photoUrl, name, size = 48, testID }: AvatarProps) { const [failed, setFailed] = useState(false); const token = getAccessToken(); // photoUrl/token 변화 시 실패 상태 초기화 후 재시도(업로드·캐시버스터 반영). useEffect(() => { setFailed(false); }, [photoUrl, token]); const dim = { width: size, height: size, borderRadius: size / 2 }; const showImage = !!photoUrl && !!token && !failed; if (showImage) { return ( setFailed(true)} accessibilityIgnoresInvertColors /> ); } return ( {initials(name)} ); } const styles = StyleSheet.create({ fallback: { alignItems: 'center', justifyContent: 'center', borderWidth: 1 }, initials: { fontWeight: '800' }, });