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:
@@ -6,6 +6,7 @@ import { ActivityIndicator, Alert, Modal, Pressable, ScrollView, StyleSheet, Tex
|
||||
import Svg, { Defs, LinearGradient, Path, Stop } from 'react-native-svg';
|
||||
import { useSQLiteContext } from 'expo-sqlite';
|
||||
import { deleteActivity, useActivity, useSetting } from '@/db/queries';
|
||||
import { useTheme } from '@/theme';
|
||||
|
||||
const MAP_STYLE = 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json';
|
||||
|
||||
@@ -28,6 +29,7 @@ export default function ActivityScreen() {
|
||||
const { id } = useLocalSearchParams<{ id: string }>();
|
||||
const router = useRouter();
|
||||
const db = useSQLiteContext();
|
||||
const theme = useTheme();
|
||||
const row = useActivity(id);
|
||||
const instanceUrl = useSetting('instance_url')?.replace(/\/$/, '') ?? '';
|
||||
const token = useSetting('api_token') ?? '';
|
||||
@@ -116,7 +118,7 @@ export default function ActivityScreen() {
|
||||
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
||||
<View style={styles.topBar}>
|
||||
<Pressable style={styles.backButton} onPress={() => router.back()}>
|
||||
<Text style={styles.backText}>← Back</Text>
|
||||
<Text style={[styles.backText, { color: theme.accent }]}>← Back</Text>
|
||||
</Pressable>
|
||||
<Pressable style={styles.deleteButton} onPress={confirmDelete}>
|
||||
<Text style={styles.deleteText}>Delete</Text>
|
||||
@@ -128,7 +130,7 @@ export default function ActivityScreen() {
|
||||
<Text style={styles.date}>{date}</Text>
|
||||
|
||||
{/* Map */}
|
||||
<RouteMap geojson={geojson} loading={loadingMap} />
|
||||
<RouteMap geojson={geojson} loading={loadingMap} accent={theme.accent} />
|
||||
|
||||
{/* Stats grid */}
|
||||
<View style={styles.grid}>
|
||||
@@ -142,7 +144,7 @@ export default function ActivityScreen() {
|
||||
</View>
|
||||
|
||||
{/* Metric charts */}
|
||||
<MetricCharts timeseries={timeseries} loading={loadingChart} />
|
||||
<MetricCharts timeseries={timeseries} loading={loadingChart} accent={theme.accent} />
|
||||
|
||||
{/* Meta */}
|
||||
<View style={styles.meta}>
|
||||
@@ -157,13 +159,13 @@ export default function ActivityScreen() {
|
||||
|
||||
// ── Map ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
function RouteMap({ geojson, loading }: { geojson: object | null; loading: boolean }) {
|
||||
function RouteMap({ geojson, loading, accent }: { geojson: object | null; loading: boolean; accent: string }) {
|
||||
const [fullscreen, setFullscreen] = useState(false);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View style={styles.mapPlaceholder}>
|
||||
<ActivityIndicator color="#60a5fa" />
|
||||
<ActivityIndicator color={accent} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -175,7 +177,7 @@ function RouteMap({ geojson, loading }: { geojson: object | null; loading: boole
|
||||
<Layer
|
||||
type="line"
|
||||
id="route-line"
|
||||
paint={{ 'line-color': '#60a5fa', 'line-width': 3 }}
|
||||
paint={{ 'line-color': accent, 'line-width': 3 }}
|
||||
layout={{ 'line-cap': 'round', 'line-join': 'round' }}
|
||||
/>
|
||||
</GeoJSONSource>
|
||||
@@ -237,13 +239,13 @@ const TAB_META: Record<TabKey, { label: string; unit: string; color: string; dec
|
||||
power: { label: 'Power', unit: 'W', color: '#facc15', decimals: 0 },
|
||||
};
|
||||
|
||||
function MetricCharts({ timeseries, loading }: { timeseries: Timeseries | null; loading: boolean }) {
|
||||
function MetricCharts({ timeseries, loading, accent }: { timeseries: Timeseries | null; loading: boolean; accent: string }) {
|
||||
const [activeTab, setActiveTab] = useState<TabKey>('elevation');
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View style={styles.chartPlaceholder}>
|
||||
<ActivityIndicator color="#60a5fa" />
|
||||
<ActivityIndicator color={accent} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -398,7 +400,7 @@ const styles = StyleSheet.create({
|
||||
notFound: { color: '#71717a', fontSize: 16 },
|
||||
topBar: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingTop: 60, paddingBottom: 12 },
|
||||
backButton: { paddingHorizontal: 16 },
|
||||
backText: { color: '#60a5fa', fontSize: 15 },
|
||||
backText: { fontSize: 15 },
|
||||
deleteButton: { paddingHorizontal: 16 },
|
||||
deleteText: { color: '#f87171', fontSize: 15 },
|
||||
sport: { color: '#71717a', fontSize: 12, fontWeight: '600', letterSpacing: 0.8, paddingHorizontal: 16, marginBottom: 4 },
|
||||
|
||||
Reference in New Issue
Block a user