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:
Davide Scaini
2026-04-25 15:41:20 +02:00
parent 5330b7b489
commit dfe5307ab4
6 changed files with 111 additions and 50 deletions
+12 -11
View File
@@ -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,