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
+7 -1
View File
@@ -4,8 +4,9 @@ import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import useSWR from 'swr';
import type { UserSummaryDTO } from '@musicshare/shared';
import { AmbientBackground } from '@/components/AmbientBackground';
import { Avatar } from '@/components/Avatar';
import { Body, Field, Title } from '@/components/ui';
import { Body, Button, Field, Row, Title } from '@/components/ui';
import { useDebounced } from '@/lib/useDebounced';
import { colors, space } from '@/lib/theme';
@@ -16,6 +17,7 @@ export default function SearchScreen() {
return (
<SafeAreaView style={styles.safe} edges={['top']}>
<AmbientBackground />
<FlatList
data={query.length >= 2 ? (data ?? []) : []}
keyExtractor={(u) => u.id}
@@ -24,6 +26,10 @@ export default function SearchScreen() {
ListHeaderComponent={
<View style={{ gap: space.md }}>
<Title>Find friends</Title>
<Row>
<Button small variant="primary" title="Scan a code" onPress={() => router.push('/scan')} />
<Button small title="My code" onPress={() => router.push('/qr')} />
</Row>
<Field
placeholder="Search by name or username"
value={q}