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
This commit is contained in:
@@ -5,10 +5,11 @@ export default function TabsLayout() {
|
||||
return (
|
||||
<NativeTabs
|
||||
backgroundColor={colors.bg}
|
||||
indicatorColor={colors.surface2}
|
||||
// The active tab gets a pink pill, like the reference's sidebar.
|
||||
indicatorColor={colors.accentSoft}
|
||||
// Android defaults icons and labels to black, which vanishes on the dark bar.
|
||||
iconColor={{ default: colors.muted, selected: colors.text }}
|
||||
labelStyle={{ default: { color: colors.muted }, selected: { color: colors.text } }}
|
||||
iconColor={{ default: colors.muted, selected: colors.accent }}
|
||||
labelStyle={{ default: { color: colors.muted }, selected: { color: colors.accent } }}
|
||||
labelVisibilityMode="labeled"
|
||||
tintColor={colors.accent}>
|
||||
<NativeTabs.Trigger name="index">
|
||||
|
||||
@@ -95,7 +95,19 @@ export default function FeedScreen() {
|
||||
ListHeaderComponent={
|
||||
<View style={styles.header}>
|
||||
<View style={styles.topBar}>
|
||||
<Text style={styles.brand}>musicshare</Text>
|
||||
{me ? (
|
||||
<Pressable style={styles.greeting} onPress={() => router.push('/profile')} accessibilityRole="link">
|
||||
<Avatar user={me} size={44} />
|
||||
<View style={{ flexShrink: 1 }}>
|
||||
<Text style={styles.hi} numberOfLines={1}>
|
||||
Hi {me.displayName ?? me.username},
|
||||
</Text>
|
||||
<Text style={styles.welcome}>Welcome back</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
) : (
|
||||
<Text style={styles.brand}>musicshare</Text>
|
||||
)}
|
||||
<ActivityBell />
|
||||
</View>
|
||||
<UpdateBanner />
|
||||
@@ -115,7 +127,7 @@ export default function FeedScreen() {
|
||||
)}
|
||||
{mine?.nowPlaying && (
|
||||
<View style={{ gap: space.sm }}>
|
||||
<Text style={styles.sectionLabel}>YOU</Text>
|
||||
<Text style={styles.sectionLabel}>Your song</Text>
|
||||
<NowPlayingCard np={mine.nowPlaying} preferred={me?.preferredService} showUser={false} />
|
||||
</View>
|
||||
)}
|
||||
@@ -143,7 +155,7 @@ export default function FeedScreen() {
|
||||
ListFooterComponent={
|
||||
quiet.length > 0 ? (
|
||||
<View style={{ gap: space.sm, marginTop: space.md }}>
|
||||
<Text style={styles.sectionLabel}>FOLLOWING · NOTHING PLAYED YET</Text>
|
||||
<Text style={styles.sectionLabel}>Following · nothing played yet</Text>
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.quiet}>
|
||||
{quiet.map((u) => (
|
||||
<Pressable key={u.id} onPress={() => router.push(`/u/${u.username}`)} style={styles.quietPerson} accessibilityLabel={u.displayName ?? u.username}>
|
||||
@@ -183,8 +195,11 @@ const styles = StyleSheet.create({
|
||||
header: { gap: space.md, marginBottom: space.xs },
|
||||
topBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
|
||||
brand: { color: colors.text, fontFamily: fonts.display, fontSize: 22, letterSpacing: -0.4 },
|
||||
sectionLabel: { color: colors.muted, fontSize: 12, fontWeight: '700', letterSpacing: 0.8 },
|
||||
greeting: { flexDirection: 'row', alignItems: 'center', gap: space.md, flexShrink: 1 },
|
||||
hi: { color: colors.text, fontFamily: fonts.display, fontSize: 19 },
|
||||
welcome: { color: colors.muted, fontFamily: fonts.body, fontSize: 13 },
|
||||
sectionLabel: { color: colors.muted, fontSize: 13, fontFamily: fonts.bodyMedium },
|
||||
quiet: { gap: space.lg, paddingVertical: space.xs },
|
||||
quietPerson: { width: 64, alignItems: 'center', gap: 4 },
|
||||
quietName: { color: colors.text, fontSize: 12, textAlign: 'center' },
|
||||
quietName: { color: colors.text, fontSize: 11.5, fontFamily: fonts.body, textAlign: 'center' },
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ import { PersonRow, emptyFollowText, useFollowList } from '@/components/FollowLi
|
||||
import { Body, Button, Chip, Field, Row, Title } from '@/components/ui';
|
||||
import { useSession } from '@/lib/session';
|
||||
import { useDebounced } from '@/lib/useDebounced';
|
||||
import { colors, space } from '@/lib/theme';
|
||||
import { colors, fonts, space } from '@/lib/theme';
|
||||
|
||||
/** Friends: who you follow and who follows you, plus search and QR codes to find new people. */
|
||||
export default function FriendsScreen() {
|
||||
@@ -95,6 +95,6 @@ const styles = StyleSheet.create({
|
||||
safe: { flex: 1, backgroundColor: colors.bg },
|
||||
list: { padding: space.lg, gap: space.md },
|
||||
row: { flexDirection: 'row', alignItems: 'center', gap: space.md, paddingVertical: space.sm },
|
||||
name: { color: colors.text, fontWeight: '700', fontSize: 15 },
|
||||
handle: { color: colors.muted, fontSize: 13 },
|
||||
name: { color: colors.text, fontFamily: fonts.display, fontSize: 15 },
|
||||
handle: { color: colors.muted, fontSize: 13, fontFamily: fonts.body },
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ import { openReaderSettings, useReader } from '@/lib/nowPlayingReader';
|
||||
import { NTFY_URL, disablePush, enablePush, pushAvailable, usePushStatus } from '@/lib/push';
|
||||
import { useSession } from '@/lib/session';
|
||||
import { checkForUpdate, currentVersion, installUpdate, updaterAvailable, useUpdateState } from '@/lib/updates';
|
||||
import { colors, radius, space } from '@/lib/theme';
|
||||
import { colors, fonts, radius, space } from '@/lib/theme';
|
||||
|
||||
export default function SettingsScreen() {
|
||||
return (
|
||||
@@ -670,7 +670,7 @@ function Account() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
small: { fontSize: 13 },
|
||||
small: { fontSize: 13, fontFamily: fonts.body },
|
||||
avatarRow: { flexDirection: 'row', alignItems: 'center', gap: space.lg },
|
||||
connection: { flexDirection: 'row', alignItems: 'center', gap: space.md },
|
||||
connectionColumn: {
|
||||
@@ -680,6 +680,6 @@ const styles = StyleSheet.create({
|
||||
paddingTop: space.md,
|
||||
},
|
||||
result: { flexDirection: 'row', alignItems: 'center', gap: space.md, paddingVertical: space.xs },
|
||||
resultText: { color: colors.text, flex: 1, fontSize: 14 },
|
||||
resultText: { color: colors.text, flex: 1, fontSize: 14, fontFamily: fonts.body },
|
||||
thumb: { width: 40, height: 40, borderRadius: radius.sm },
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SpaceGrotesk_500Medium, SpaceGrotesk_700Bold, useFonts } from '@expo-google-fonts/space-grotesk';
|
||||
import { Poppins_400Regular, Poppins_500Medium, Poppins_600SemiBold, useFonts } from '@expo-google-fonts/poppins';
|
||||
import { DarkTheme, Stack, ThemeProvider } from 'expo-router';
|
||||
import * as SplashScreen from 'expo-splash-screen';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
@@ -37,7 +37,7 @@ function AppNavigator() {
|
||||
const { status, me, retry } = useSession();
|
||||
const signedIn = status === 'signedIn';
|
||||
const onboarded = Boolean(me?.onboardedAt);
|
||||
const [fontsLoaded, fontError] = useFonts({ SpaceGrotesk_500Medium, SpaceGrotesk_700Bold });
|
||||
const [fontsLoaded, fontError] = useFonts({ Poppins_400Regular, Poppins_500Medium, Poppins_600SemiBold });
|
||||
const ready = status !== 'loading' && (fontsLoaded || Boolean(fontError));
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Avatar } from '@/components/Avatar';
|
||||
import { Body, Button } from '@/components/ui';
|
||||
import { api } from '@/lib/api';
|
||||
import { showError } from '@/lib/errors';
|
||||
import { colors, space } from '@/lib/theme';
|
||||
import { colors, fonts, space } from '@/lib/theme';
|
||||
|
||||
export default function BlockedScreen() {
|
||||
const { data: blocked = [], mutate, isLoading } = useSWR<UserSummaryDTO[]>('/me/blocks');
|
||||
@@ -45,5 +45,5 @@ const styles = StyleSheet.create({
|
||||
list: { padding: space.lg, gap: space.md },
|
||||
row: { flexDirection: 'row', alignItems: 'center', gap: space.md },
|
||||
person: { flexDirection: 'row', alignItems: 'center', gap: space.md },
|
||||
name: { color: colors.text, fontSize: 15, fontWeight: '600', flexShrink: 1 },
|
||||
name: { color: colors.text, fontSize: 15, fontFamily: fonts.bodyMedium, flexShrink: 1 },
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { notificationPath, notificationText, timeAgo, type NotificationDTO, type
|
||||
import { Avatar } from '@/components/Avatar';
|
||||
import { Body } from '@/components/ui';
|
||||
import { api } from '@/lib/api';
|
||||
import { colors, space } from '@/lib/theme';
|
||||
import { colors, fonts, space } from '@/lib/theme';
|
||||
|
||||
/** Reactions, new followers and songs sent to you, newest first. Opening it marks them read. */
|
||||
export default function NotificationsScreen() {
|
||||
@@ -77,8 +77,8 @@ const styles = StyleSheet.create({
|
||||
row: { flexDirection: 'row', gap: space.md, alignItems: 'center', padding: space.md, borderRadius: 14 },
|
||||
unread: { backgroundColor: colors.surface2 },
|
||||
text: { flex: 1, minWidth: 0, gap: 2 },
|
||||
title: { color: colors.text, fontWeight: '700', fontSize: 15 },
|
||||
body: { color: colors.text, opacity: 0.85, fontSize: 14 },
|
||||
title: { color: colors.text, fontFamily: fonts.display, fontSize: 15 },
|
||||
body: { color: colors.text, opacity: 0.85, fontSize: 14, fontFamily: fonts.body },
|
||||
time: { color: colors.muted, fontSize: 12 },
|
||||
art: { width: 44, height: 44, borderRadius: 8 },
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ import { WEB_URL } from '@/lib/config';
|
||||
import { showError } from '@/lib/errors';
|
||||
import { successHaptic } from '@/lib/haptics';
|
||||
import { useSession } from '@/lib/session';
|
||||
import { colors, space } from '@/lib/theme';
|
||||
import { colors, fonts, space } from '@/lib/theme';
|
||||
import { useDebounced } from '@/lib/useDebounced';
|
||||
|
||||
/** Step 3: an empty feed is the fastest way to lose someone, so find friends now. */
|
||||
@@ -90,6 +90,6 @@ export default function FriendsStep() {
|
||||
const styles = StyleSheet.create({
|
||||
row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: space.md },
|
||||
who: { flexDirection: 'row', alignItems: 'center', gap: space.md, flexShrink: 1 },
|
||||
name: { color: colors.text, fontWeight: '700', fontSize: 15 },
|
||||
handle: { color: colors.muted, fontSize: 13 },
|
||||
name: { color: colors.text, fontFamily: fonts.display, fontSize: 15 },
|
||||
handle: { color: colors.muted, fontSize: 13, fontFamily: fonts.body },
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import { showError } from '@/lib/errors';
|
||||
import { selectionHaptic } from '@/lib/haptics';
|
||||
import { LISTEN_APPS, finishOnboarding } from '@/lib/onboarding';
|
||||
import { useSession } from '@/lib/session';
|
||||
import { colors, radius, space } from '@/lib/theme';
|
||||
import { colors, fonts, radius, space } from '@/lib/theme';
|
||||
|
||||
/** Step 1: where do you listen? Sets where friends' songs open, and picks the right setup next. */
|
||||
export default function ListenAppStep() {
|
||||
@@ -69,7 +69,7 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: space.md,
|
||||
},
|
||||
optionPicked: { borderColor: colors.accent, backgroundColor: 'rgba(124,77,255,0.18)' },
|
||||
optionText: { color: colors.text, fontWeight: '600', fontSize: 15 },
|
||||
optionPicked: { borderColor: colors.accent, backgroundColor: colors.accentSoft },
|
||||
optionText: { color: colors.text, fontFamily: fonts.bodyMedium, fontSize: 15 },
|
||||
optionTextPicked: { color: '#fff' },
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { api } from '@/lib/api';
|
||||
import { showError } from '@/lib/errors';
|
||||
import { tapHaptic } from '@/lib/haptics';
|
||||
import { useSession } from '@/lib/session';
|
||||
import { colors, radius, space } from '@/lib/theme';
|
||||
import { colors, fonts, radius, space } from '@/lib/theme';
|
||||
|
||||
/** One play and its comments: where the 💬 chip and comment notifications lead. */
|
||||
export default function PlayScreen() {
|
||||
@@ -163,9 +163,9 @@ const styles = StyleSheet.create({
|
||||
header: { gap: space.md, marginBottom: space.sm },
|
||||
playedBy: { flexDirection: 'row', alignItems: 'center', gap: space.sm },
|
||||
comment: { flexDirection: 'row', gap: space.md, alignItems: 'flex-start' },
|
||||
name: { color: colors.text, fontWeight: '700' },
|
||||
muted: { color: colors.muted, fontSize: 13 },
|
||||
body: { color: colors.text, fontSize: 15, lineHeight: 21 },
|
||||
name: { color: colors.text, fontFamily: fonts.display },
|
||||
muted: { color: colors.muted, fontSize: 13, fontFamily: fonts.body },
|
||||
body: { color: colors.text, fontSize: 15, lineHeight: 21, fontFamily: fonts.body },
|
||||
more: { color: colors.muted, fontSize: 18, paddingHorizontal: space.xs },
|
||||
composer: {
|
||||
flexDirection: 'row',
|
||||
@@ -190,6 +190,6 @@ const styles = StyleSheet.create({
|
||||
borderColor: colors.border,
|
||||
},
|
||||
send: { paddingHorizontal: space.md, paddingVertical: space.sm + 2, borderRadius: radius.pill, backgroundColor: colors.accent },
|
||||
sendText: { color: '#fff', fontWeight: '700' },
|
||||
sendText: { color: '#fff', fontFamily: fonts.display },
|
||||
closed: { margin: space.lg, marginTop: 0 },
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Screen } from '@/components/Screen';
|
||||
import { Body, Button } from '@/components/ui';
|
||||
import { WEB_URL } from '@/lib/config';
|
||||
import { useSession } from '@/lib/session';
|
||||
import { colors, space } from '@/lib/theme';
|
||||
import { colors, fonts, space } from '@/lib/theme';
|
||||
|
||||
/** "My code": friends scan it (in musicshare or any camera app) to open your profile. */
|
||||
export default function MyQrScreen() {
|
||||
@@ -40,8 +40,8 @@ export default function MyQrScreen() {
|
||||
const styles = StyleSheet.create({
|
||||
content: { flexGrow: 1, alignItems: 'center', justifyContent: 'center', gap: space.xl },
|
||||
who: { alignItems: 'center', gap: space.xs },
|
||||
name: { color: colors.text, fontSize: 22, fontWeight: '800' },
|
||||
handle: { color: colors.muted, fontSize: 15 },
|
||||
name: { color: colors.text, fontSize: 22, fontFamily: fonts.display },
|
||||
handle: { color: colors.muted, fontSize: 15, fontFamily: fonts.body },
|
||||
hint: { textAlign: 'center', maxWidth: 300 },
|
||||
actions: { alignSelf: 'stretch', gap: space.sm },
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Body, Button, Title } from '@/components/ui';
|
||||
import { FOSS_BUILD } from '@/lib/build';
|
||||
import { decodeQrFromJpeg, pickSnapshotSize } from '@/lib/qrSnapshot';
|
||||
import { useSession } from '@/lib/session';
|
||||
import { colors, radius, space } from '@/lib/theme';
|
||||
import { colors, fonts, radius, space } from '@/lib/theme';
|
||||
|
||||
/** Scan a friend's musicshare QR code and open their profile (with its Follow button). */
|
||||
export default function ScanScreen() {
|
||||
@@ -115,7 +115,7 @@ const styles = StyleSheet.create({
|
||||
prompt: {
|
||||
color: colors.accentText,
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
fontFamily: fonts.bodyMedium,
|
||||
textAlign: 'center',
|
||||
backgroundColor: 'rgba(0,0,0,0.55)',
|
||||
paddingHorizontal: space.md,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { api } from '@/lib/api';
|
||||
import { showError } from '@/lib/errors';
|
||||
import { successHaptic, tapHaptic } from '@/lib/haptics';
|
||||
import { useSession } from '@/lib/session';
|
||||
import { colors, radius, space } from '@/lib/theme';
|
||||
import { colors, fonts, radius, space } from '@/lib/theme';
|
||||
|
||||
/** Send a song to some of your followers, with an optional note. */
|
||||
export default function SendSongScreen() {
|
||||
@@ -98,10 +98,10 @@ export default function SendSongScreen() {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
list: { padding: space.lg, gap: space.sm, paddingBottom: space.xl * 2 },
|
||||
song: { color: colors.text, fontSize: 17, fontWeight: '700' },
|
||||
muted: { color: colors.muted, fontWeight: '400' },
|
||||
song: { color: colors.text, fontSize: 17, fontFamily: fonts.display },
|
||||
muted: { color: colors.muted, fontFamily: fonts.body },
|
||||
person: { flexDirection: 'row', alignItems: 'center', gap: space.md, padding: space.sm, borderRadius: radius.md },
|
||||
on: { backgroundColor: colors.surface2 },
|
||||
name: { flex: 1, color: colors.text, fontSize: 15, fontWeight: '600' },
|
||||
check: { color: colors.muted, fontSize: 18 },
|
||||
name: { flex: 1, color: colors.text, fontSize: 15, fontFamily: fonts.bodyMedium },
|
||||
check: { color: colors.muted, fontSize: 18, fontFamily: fonts.body },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user