Mobile: redesign, onboarding, QR codes, logo, Spotify setup

- Redesign matching the web: album-art backdrops, Reanimated equalizer,
  spinning live ring, drifting ambient background, gradient buttons with
  spring press feedback and haptics, Space Grotesk headings, animated feed
  (enter + layout transitions); fix invisible tab labels
- Four-step onboarding for new accounts: which app you listen with (sets
  where songs open), the matching share setup, find friends (search,
  scan, invite), and a featured song. Skippable; stored server-side
- QR codes: My code (share link) and an in-app scanner (camera only, no
  microphone permission)
- Spotify setup via Last.fm or ListenBrainz with guided linking; Android
  shares Spotify automatically through the phone reader
- New logo as app icon, adaptive/monochrome icon and splash
- app.config.ts allows cleartext only for local http:// test builds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
2026-10-05 19:16:40 -04:00
co-authored by Claude Opus 5.5
parent 17ae36064c
commit 3568b71e90
44 changed files with 1481 additions and 358 deletions
+26 -9
View File
@@ -2,7 +2,7 @@ import { Image } from 'expo-image';
import { Linking, Pressable, StyleSheet, Text, View } from 'react-native';
import { SERVICE_LABELS, STREAMING_SERVICES, openUrl, type StreamingService, type TrackDTO } from '@musicshare/shared';
import { showError } from '@/lib/errors';
import { colors, radius, space } from '@/lib/theme';
import { colors, fonts, radius, space } from '@/lib/theme';
import { Button } from './ui';
/**
@@ -46,7 +46,7 @@ export function TrackCard({ track, preferred }: { track: TrackDTO; preferred: St
const { url, exact } = openUrl(track, s);
return (
<Pressable key={s} onPress={() => open(url)} hitSlop={6} accessibilityRole="link">
<Text style={[styles.service, !exact && { opacity: 0.6 }]}>{SERVICE_LABELS[s]}</Text>
<Text style={[styles.service, !exact && { opacity: 0.55 }]}>{SERVICE_LABELS[s]}</Text>
</Pressable>
);
})}
@@ -57,15 +57,32 @@ export function TrackCard({ track, preferred }: { track: TrackDTO; preferred: St
}
const styles = StyleSheet.create({
track: { flexDirection: 'row', gap: space.md, alignItems: 'flex-start' },
artwork: { width: 88, height: 88, borderRadius: radius.md, backgroundColor: colors.surface2 },
artworkEmpty: { alignItems: 'center', justifyContent: 'center' },
note: { color: colors.muted, fontSize: 28 },
track: { flexDirection: 'row', gap: space.md + 2, alignItems: 'flex-start' },
artwork: {
width: 96,
height: 96,
borderRadius: 14,
backgroundColor: colors.surface2,
borderWidth: StyleSheet.hairlineWidth,
borderColor: 'rgba(255,255,255,0.12)',
},
artworkEmpty: { alignItems: 'center', justifyContent: 'center', backgroundColor: '#3a1a6e' },
note: { color: 'rgba(255,255,255,0.85)', fontSize: 30 },
body: { flex: 1, minWidth: 0, gap: 2 },
title: { color: colors.text, fontWeight: '700', fontSize: 16 },
title: { color: colors.text, fontFamily: fonts.display, fontSize: 18, letterSpacing: -0.2 },
artist: { color: colors.text, opacity: 0.85, fontSize: 14 },
muted: { color: colors.muted },
primary: { alignSelf: 'flex-start', marginTop: space.sm },
services: { flexDirection: 'row', flexWrap: 'wrap', columnGap: space.md, rowGap: space.xs, marginTop: space.sm },
service: { color: colors.muted, fontSize: 12 },
services: { flexDirection: 'row', flexWrap: 'wrap', gap: space.xs + 2, marginTop: space.sm + 2 },
service: {
color: colors.text,
fontSize: 12,
paddingHorizontal: space.sm + 2,
paddingVertical: 4,
borderRadius: radius.pill,
overflow: 'hidden',
backgroundColor: 'rgba(255,255,255,0.08)',
borderWidth: StyleSheet.hairlineWidth,
borderColor: 'rgba(255,255,255,0.12)',
},
});