Native tabs for feed, friend search, profile and settings, with feature parity with the web app. Sessions are stored in SecureStore, realtime disconnects in the background, and Spotify/Plex sign-in returns to the app via the URL scheme. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
66 lines
2.5 KiB
TypeScript
66 lines
2.5 KiB
TypeScript
import { router } from 'expo-router';
|
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
import { SERVICE_LABELS, timeAgo, type NowPlayingDTO, type StreamingService } from '@musicshare/shared';
|
|
import { colors, space } from '@/lib/theme';
|
|
import { Avatar } from './Avatar';
|
|
import { TrackCard } from './TrackCard';
|
|
import { Card } from './ui';
|
|
|
|
export function NowPlayingCard({
|
|
np,
|
|
preferred,
|
|
showUser = true,
|
|
pinned,
|
|
onTogglePin,
|
|
}: {
|
|
np: NowPlayingDTO;
|
|
preferred: StreamingService | null | undefined;
|
|
showUser?: boolean;
|
|
pinned?: boolean;
|
|
onTogglePin?: () => void;
|
|
}) {
|
|
return (
|
|
<Card highlighted={pinned}>
|
|
<View style={styles.header}>
|
|
{showUser && (
|
|
<Pressable style={styles.user} onPress={() => router.push(`/u/${np.user.username}`)} accessibilityRole="link">
|
|
<Avatar user={np.user} size={28} />
|
|
<Text style={styles.name} numberOfLines={1}>
|
|
{np.user.displayName ?? np.user.username}
|
|
</Text>
|
|
</Pressable>
|
|
)}
|
|
<View style={styles.statusRow}>
|
|
{np.isPlaying && <View style={styles.dot} />}
|
|
<Text style={[styles.status, np.isPlaying && { color: colors.live }]} numberOfLines={1}>
|
|
{np.isPlaying ? 'Listening now' : `Played ${timeAgo(np.updatedAt)}`}
|
|
<Text style={styles.via}> · {SERVICE_LABELS[np.source]}</Text>
|
|
</Text>
|
|
{onTogglePin && (
|
|
<Pressable
|
|
onPress={onTogglePin}
|
|
hitSlop={10}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={pinned ? 'Unpin from top of feed' : 'Pin to top of feed'}
|
|
accessibilityState={{ selected: pinned }}>
|
|
<Text style={[styles.pin, pinned && { color: colors.pin }]}>{pinned ? '★' : '☆'}</Text>
|
|
</Pressable>
|
|
)}
|
|
</View>
|
|
</View>
|
|
<TrackCard track={np.track} preferred={preferred} />
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: space.sm },
|
|
user: { flexDirection: 'row', alignItems: 'center', gap: space.sm, flexShrink: 1 },
|
|
name: { color: colors.text, fontWeight: '700', fontSize: 15, flexShrink: 1 },
|
|
statusRow: { flexDirection: 'row', alignItems: 'center', gap: space.xs, flexShrink: 0 },
|
|
dot: { width: 8, height: 8, borderRadius: 4, backgroundColor: colors.live },
|
|
status: { color: colors.muted, fontSize: 13 },
|
|
via: { color: colors.muted },
|
|
pin: { color: colors.muted, fontSize: 20, marginLeft: space.xs },
|
|
});
|