import React, { useEffect, useState } from 'react'; import { View, Text, StyleSheet, Switch, TouchableOpacity, Alert, ScrollView, ActivityIndicator, } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { login, logout, loadAuthState } from '../services/auth'; import { colors, PALETTES, type PaletteKey, type FontSizeKey } from '../theme'; import { useTheme, type MapOrientation } from '../ThemeContext'; import { MAP_STYLES, MAP_TILE_STYLE_ORDER } from '../mapStyles'; type Tab = 'ui' | 'app' | 'sync'; export function SettingsScreen() { const [tab, setTab] = useState('ui'); const { accent } = useTheme(); return ( {/* Tab bar */} {(['ui', 'app', 'sync'] as Tab[]).map((t) => ( setTab(t)} > {t === 'ui' ? 'Interface' : t === 'app' ? 'App' : 'Sync'} ))} {tab === 'ui' && } {tab === 'app' && } {tab === 'sync' && } ); } // ─── UI tab ───────────────────────────────────────────────────────────────── function UITab() { const { accent, palette, setPalette, fontSize, setFontSize, boldLabels, setBoldLabels, mapTileStyle, setMapTileStyle } = useTheme(); const fontSizes: FontSizeKey[] = ['small', 'medium', 'large']; const palettes = Object.entries(PALETTES) as [PaletteKey, typeof PALETTES[PaletteKey]][]; return ( Colour palette {palettes.map(([key, val]) => ( setPalette(key)} > {val.label} ))} Font size {fontSizes.map((s) => ( setFontSize(s)} > {s.charAt(0).toUpperCase() + s.slice(1)} ))} Map style {MAP_TILE_STYLE_ORDER.map((key) => { const def = MAP_STYLES[key]; const active = mapTileStyle === key; return ( setMapTileStyle(key)} > {def.label} {def.description} {active && } ); })} Stat labels Bold labels ); } // ─── App tab ───────────────────────────────────────────────────────────────── const MAP_ORIENTATION_OPTIONS: { key: MapOrientation; label: string; sub: string }[] = [ { key: 'north', label: 'North up', sub: 'Map always points north' }, { key: 'compass', label: 'Compass', sub: 'Rotates with device heading' }, { key: 'course', label: 'Course up', sub: 'Rotates to direction of travel' }, ]; function AppTab() { const { accent, accentDim, mapOrientation, setMapOrientation } = useTheme(); const [kmNotifications, setKmNotifications] = useState(true); useEffect(() => { AsyncStorage.getItem('kmNotifications').then((v) => { if (v !== null) setKmNotifications(v === 'true'); }); }, []); async function handleKmToggle(value: boolean) { setKmNotifications(value); await AsyncStorage.setItem('kmNotifications', String(value)); } return ( Map orientation {MAP_ORIENTATION_OPTIONS.map(({ key, label, sub }) => { const active = mapOrientation === key; return ( setMapOrientation(key)} > {label} {sub} {active && } ); })} Notifications Kilometre alerts Notify at every km during recording ); } // ─── Sync tab ───────────────────────────────────────────────────────────────── function SyncTab() { const { accent } = useTheme(); const [connectedAs, setConnectedAs] = useState(null); const [connecting, setConnecting] = useState(false); useEffect(() => { loadAuthState().then((auth) => { if (auth) setConnectedAs(auth.handle); }); }, []); async function handleConnect() { setConnecting(true); const result = await login(); setConnecting(false); if (result.ok) setConnectedAs(result.displayName ?? ''); else if (result.error !== 'Cancelled') Alert.alert('Sign in failed', result.error ?? 'Unknown error'); } async function handleDisconnect() { Alert.alert('Disconnect', 'Remove saved credentials?', [ { text: 'Cancel', style: 'cancel' }, { text: 'Disconnect', style: 'destructive', onPress: async () => { await logout(); setConnectedAs(null); }}, ]); } return ( bincio instance {connectedAs ? ( Connected {connectedAs} Disconnect ) : ( <> Sign in with your bincio account to sync recordings. {connecting ? : Sign in with bincio} )} bincio-autarchive Local sync Coming soon ); } // ─── Shared styles ──────────────────────────────────────────────────────────── const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.bg }, tabBar: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: colors.border }, tabBtn: { flex: 1, alignItems: 'center', paddingVertical: 14, borderBottomWidth: 2, borderBottomColor: 'transparent' }, tabLabel: { color: colors.textMuted, fontSize: 13, fontWeight: '600' }, content: { padding: 16, gap: 10 }, sectionTitle: { color: colors.textMuted, fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 16 }, pillRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 }, pill: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingVertical: 8, paddingHorizontal: 14, borderRadius: 20, borderWidth: 1, borderColor: colors.borderStrong }, paletteDot: { width: 10, height: 10, borderRadius: 5 }, pillText: { color: colors.textSub, fontSize: 13, fontWeight: '500' }, label: { color: colors.textSub, fontSize: 13, marginBottom: 2 }, input: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, color: colors.text, borderRadius: 8, padding: 12, fontSize: 15 }, hint: { color: colors.textMuted, fontSize: 12, lineHeight: 18 }, row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14 }, rowLabel: { color: colors.text, fontSize: 15 }, rowSub: { color: colors.textMuted, fontSize: 12, marginTop: 2 }, connectedBox: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14 }, connectedLabel: { fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.6 }, connectedName: { color: colors.text, fontSize: 15, fontWeight: '600', marginTop: 2 }, disconnectBtn: { paddingVertical: 6, paddingHorizontal: 12, borderRadius: 8, borderWidth: 1, borderColor: colors.errorBg }, disconnectBtnText: { color: colors.error, fontWeight: '600', fontSize: 13 }, connectBtn: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14, alignItems: 'center', marginTop: 4 }, connectBtnDisabled:{ opacity: 0.5 }, connectBtnText: { color: colors.text, fontSize: 15, fontWeight: '600' }, });