feat: seasonal race palette (Giro/Tour/Vuelta) + mobile picker
- theme.ts: useTheme() hook with race calendar (May–Sep windows), auto-detects Giro/Tour/Vuelta by date; stores override in SQLite - All screens (feed, import, activity, tab bar) now use accent/dim from useTheme() instead of hardcoded #60a5fa - Settings: Palette section with Auto/Default/Giro/Tour/Vuelta buttons to override the auto-detected palette for testing
This commit is contained in:
+12
-11
@@ -5,9 +5,11 @@ import { useCallback, useState } from 'react';
|
||||
import { Alert, FlatList, Pressable, RefreshControl, StyleSheet, Text, View } from 'react-native';
|
||||
import { deleteActivities, useActivities, type ActivitySummary } from '@/db/queries';
|
||||
import { syncFeed } from '@/db/sync';
|
||||
import { useTheme } from '@/theme';
|
||||
|
||||
export default function FeedScreen() {
|
||||
const db = useSQLiteContext();
|
||||
const theme = useTheme();
|
||||
const activities = useActivities();
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [syncMsg, setSyncMsg] = useState<string | null>(null);
|
||||
@@ -86,10 +88,10 @@ export default function FeedScreen() {
|
||||
<>
|
||||
<Text style={styles.header}>Feed</Text>
|
||||
<Pressable
|
||||
style={[styles.syncButton, syncing && styles.syncButtonDisabled]}
|
||||
style={[styles.syncButton, { backgroundColor: theme.dim }, syncing && styles.syncButtonDisabled]}
|
||||
onPress={syncing ? undefined : doSync}
|
||||
>
|
||||
<Text style={styles.syncText}>{syncing ? 'Syncing…' : '↓ Sync'}</Text>
|
||||
<Text style={[styles.syncText, { color: theme.accent }]}>{syncing ? 'Syncing…' : '↓ Sync'}</Text>
|
||||
</Pressable>
|
||||
</>
|
||||
)}
|
||||
@@ -156,6 +158,7 @@ function ActivityCard({
|
||||
onLongPress: () => void;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const theme = useTheme();
|
||||
const km = activity.distance_m != null ? (activity.distance_m / 1000).toFixed(1) : null;
|
||||
const elev = activity.elevation_gain_m != null ? Math.round(activity.elevation_gain_m) : null;
|
||||
const date = new Date(activity.started_at).toLocaleDateString(undefined, {
|
||||
@@ -172,14 +175,14 @@ function ActivityCard({
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={[styles.card, checked && styles.cardSelected]}
|
||||
style={[styles.card, checked && { borderColor: theme.accent }]}
|
||||
onPress={handlePress}
|
||||
onLongPress={onLongPress}
|
||||
>
|
||||
<View style={styles.cardTop}>
|
||||
<View style={styles.cardLeft}>
|
||||
{selecting && (
|
||||
<View style={[styles.checkbox, checked && styles.checkboxChecked]}>
|
||||
<View style={[styles.checkbox, checked && { backgroundColor: theme.accent, borderColor: theme.accent }]}>
|
||||
{checked && <Text style={styles.checkmark}>✓</Text>}
|
||||
</View>
|
||||
)}
|
||||
@@ -188,7 +191,7 @@ function ActivityCard({
|
||||
<View style={styles.cardMeta}>
|
||||
<Text style={styles.cardDate}>{date}</Text>
|
||||
{activity.origin === 'remote'
|
||||
? <Text style={styles.remoteBadge}>cloud</Text>
|
||||
? <Text style={[styles.remoteBadge, { color: theme.accent, borderColor: theme.accent }]}>cloud</Text>
|
||||
: !activity.synced_at && <Text style={styles.localBadge}>local</Text>
|
||||
}
|
||||
</View>
|
||||
@@ -226,11 +229,11 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
header: { color: '#fff', fontSize: 22, fontWeight: '700' },
|
||||
syncButton: {
|
||||
backgroundColor: '#1e3a5f', borderRadius: 8,
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 14, paddingVertical: 7,
|
||||
},
|
||||
syncButtonDisabled: { opacity: 0.5 },
|
||||
syncText: { color: '#60a5fa', fontSize: 13, fontWeight: '600' },
|
||||
syncText: { fontSize: 13, fontWeight: '600' },
|
||||
cancelButton: {
|
||||
backgroundColor: '#27272a', borderRadius: 8,
|
||||
paddingHorizontal: 14, paddingVertical: 7,
|
||||
@@ -245,15 +248,14 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: '#18181b', borderRadius: 12,
|
||||
padding: 16, borderWidth: 1, borderColor: '#27272a',
|
||||
},
|
||||
cardSelected: { borderColor: '#60a5fa' },
|
||||
cardTop: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 6 },
|
||||
cardLeft: { flexDirection: 'row', alignItems: 'center', gap: 10 },
|
||||
sportIcon: { fontSize: 20 },
|
||||
cardMeta: { flexDirection: 'row', alignItems: 'center', gap: 8 },
|
||||
cardDate: { color: '#71717a', fontSize: 12 },
|
||||
remoteBadge: {
|
||||
color: '#60a5fa', fontSize: 10, borderWidth: 1,
|
||||
borderColor: '#1e3a5f', borderRadius: 4, paddingHorizontal: 4,
|
||||
fontSize: 10, borderWidth: 1,
|
||||
borderRadius: 4, paddingHorizontal: 4,
|
||||
},
|
||||
localBadge: {
|
||||
color: '#a1a1aa', fontSize: 10, borderWidth: 1,
|
||||
@@ -268,7 +270,6 @@ const styles = StyleSheet.create({
|
||||
width: 20, height: 20, borderRadius: 4, borderWidth: 1.5,
|
||||
borderColor: '#52525b', alignItems: 'center', justifyContent: 'center',
|
||||
},
|
||||
checkboxChecked: { backgroundColor: '#60a5fa', borderColor: '#60a5fa' },
|
||||
checkmark: { color: '#fff', fontSize: 12, fontWeight: '700' },
|
||||
empty: {
|
||||
flex: 1, alignItems: 'center', justifyContent: 'center', padding: 32,
|
||||
|
||||
Reference in New Issue
Block a user