Files
musicshare/apps/mobile/src/components/Avatar.tsx
T
chrisandClaude Opus 5.5 864ab88627 New look: slate-indigo panels, pink accent, Poppins, and a gradient card for your song
Inspired by a dashboard design the user shared.
- App: new palette and fonts, borderless rounded panels with soft shadows, a pink-to-blue
  gradient card for your own song (text and controls switch to white on it), flat cards for
  friends, a 'Hi Chris, welcome back' header, an outline bell, and a pink pill on the active tab.
- Website: the same look, plus a wide-screen layout with a sidebar, the page in a rounded panel,
  and a right column with your profile, recent activity and recent plays. Phones keep the top
  bar and bottom tabs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Y2iUsKEWDuRGjK5FPmcUP
2026-10-15 15:01:20 -04:00

49 lines
2.2 KiB
TypeScript

import { Image } from 'expo-image';
import { LinearGradient } from 'expo-linear-gradient';
import { useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Animated, { Easing, cancelAnimation, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
import type { UserSummaryDTO } from '@musicshare/shared';
import { colors, fonts } from '@/lib/theme';
/** `live` wraps the avatar in a spinning gradient ring (someone is listening right now). */
export function Avatar({ user, size = 40, live }: { user: UserSummaryDTO; size?: number; live?: boolean }) {
const rotation = useSharedValue(0);
useEffect(() => {
if (live) rotation.set(withRepeat(withTiming(360, { duration: 3500, easing: Easing.linear }), -1, false));
else cancelAnimation(rotation);
return () => cancelAnimation(rotation);
}, [live, rotation]);
const spin = useAnimatedStyle(() => ({ transform: [{ rotate: `${rotation.value}deg` }] }));
const shape = { width: size, height: size, borderRadius: size / 2 };
const face = user.avatarUrl ? (
<Image source={user.avatarUrl} style={shape} contentFit="cover" />
) : (
<LinearGradient colors={[colors.accent, '#8b5cf6']} start={{ x: 0, y: 0 }} end={{ x: 1, y: 1 }} style={[styles.empty, shape]}>
<Text style={[styles.letter, { fontSize: size * 0.45 }]}>{(user.displayName ?? user.username).slice(0, 1).toUpperCase()}</Text>
</LinearGradient>
);
if (!live) return face;
const ring = size + 6;
return (
<View style={{ width: ring, height: ring, alignItems: 'center', justifyContent: 'center' }}>
<Animated.View style={[StyleSheet.absoluteFill, spin]}>
<LinearGradient
colors={[colors.accent, colors.accent2, colors.live, colors.accent]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={{ width: ring, height: ring, borderRadius: ring / 2 }}
/>
</Animated.View>
<View style={{ borderRadius: (size + 2) / 2, borderWidth: 2, borderColor: colors.bg }}>{face}</View>
</View>
);
}
const styles = StyleSheet.create({
empty: { alignItems: 'center', justifyContent: 'center' },
letter: { color: '#fff', fontFamily: fonts.display },
});