design: align visual style with bincio_autarchive

- Add src/theme.ts with centralised color palette
- Backgrounds: #111 → #09090b, surfaces #1e1e1e → #18181b
- All cards get 1px #27272a borders (matches autarchive cards)
- Text: #fff/#888/#555 → #f4f4f5/#a1a1aa/#71717a
- Accent: #3b82f6 → #60a5fa (autarchive default palette)
- Tab icons: colored emoji → monochromatic Unicode (◉ ☰ ⚙)
- Action buttons use muted palette (#16a34a / #d97706 / #dc2626)
- Keep-awake toggle uses ◑/◌ symbols, border highlights accent on active
- Connect/Scan buttons match autarchive surface+border style
This commit is contained in:
Davide Scaini
2026-06-03 09:50:05 +02:00
parent 6e47ced264
commit ea938e5644
7 changed files with 165 additions and 137 deletions
+23 -25
View File
@@ -10,36 +10,34 @@ import { SensorPairingScreen } from '../screens/SensorPairingScreen';
import { SavedRecordingsScreen } from '../screens/SavedRecordingsScreen'; import { SavedRecordingsScreen } from '../screens/SavedRecordingsScreen';
import { SettingsScreen } from '../screens/SettingsScreen'; import { SettingsScreen } from '../screens/SettingsScreen';
import { RootStackParamList, TabParamList } from '../types'; import { RootStackParamList, TabParamList } from '../types';
import { colors } from '../theme';
const Stack = createNativeStackNavigator<RootStackParamList>(); const Stack = createNativeStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator<TabParamList>(); const Tab = createBottomTabNavigator<TabParamList>();
const TAB_ICONS: Record<string, string> = {
Recording: '◉',
Saved: '☰',
Settings: '⚙',
};
function Tabs() { function Tabs() {
return ( return (
<Tab.Navigator <Tab.Navigator
screenOptions={{ screenOptions={({ route }) => ({
headerStyle: { backgroundColor: '#111' }, headerStyle: { backgroundColor: colors.bg },
headerTintColor: '#fff', headerTintColor: colors.text,
tabBarStyle: { backgroundColor: '#111', borderTopColor: '#222' }, tabBarStyle: { backgroundColor: colors.surface, borderTopColor: colors.border },
tabBarActiveTintColor: '#3b82f6', tabBarActiveTintColor: colors.accent,
tabBarInactiveTintColor: '#555', tabBarInactiveTintColor: colors.textMuted,
}} tabBarIcon: ({ color }) => (
<Text style={{ color, fontSize: 18 }}>{TAB_ICONS[route.name]}</Text>
),
})}
> >
<Tab.Screen <Tab.Screen name="Recording" component={RecordingScreen} />
name="Recording" <Tab.Screen name="Saved" component={SavedRecordingsScreen} />
component={RecordingScreen} <Tab.Screen name="Settings" component={SettingsScreen} />
options={{ tabBarIcon: ({ color }) => <Text style={{ color, fontSize: 20 }}></Text> }}
/>
<Tab.Screen
name="Saved"
component={SavedRecordingsScreen}
options={{ tabBarIcon: ({ color }) => <Text style={{ color, fontSize: 20 }}>📋</Text> }}
/>
<Tab.Screen
name="Settings"
component={SettingsScreen}
options={{ tabBarIcon: ({ color }) => <Text style={{ color, fontSize: 20 }}></Text> }}
/>
</Tab.Navigator> </Tab.Navigator>
); );
} }
@@ -49,9 +47,9 @@ export function AppNavigator() {
<NavigationContainer> <NavigationContainer>
<Stack.Navigator <Stack.Navigator
screenOptions={{ screenOptions={{
headerStyle: { backgroundColor: '#111' }, headerStyle: { backgroundColor: colors.bg },
headerTintColor: '#fff', headerTintColor: colors.text,
contentStyle: { backgroundColor: '#111' }, contentStyle: { backgroundColor: colors.bg },
}} }}
> >
<Stack.Screen name="Tabs" component={Tabs} options={{ headerShown: false }} /> <Stack.Screen name="Tabs" component={Tabs} options={{ headerShown: false }} />
+15 -14
View File
@@ -6,6 +6,7 @@ import { saveGpx } from '../services/gpx';
import { insertRecording } from '../services/db'; import { insertRecording } from '../services/db';
import { SavedRecording } from '../types'; import { SavedRecording } from '../types';
import { randomUUID } from 'expo-crypto'; import { randomUUID } from 'expo-crypto';
import { colors } from '../theme';
export function PostRecordingScreen() { export function PostRecordingScreen() {
const nav = useNavigation(); const nav = useNavigation();
@@ -65,7 +66,7 @@ export function PostRecordingScreen() {
<TextInput <TextInput
style={styles.input} style={styles.input}
placeholder="Activity title" placeholder="Activity title"
placeholderTextColor="#555" placeholderTextColor={colors.placeholder}
value={title} value={title}
onChangeText={setTitle} onChangeText={setTitle}
autoFocus autoFocus
@@ -75,7 +76,7 @@ export function PostRecordingScreen() {
<Text style={styles.btnText}>{saving ? 'Saving…' : 'Save'}</Text> <Text style={styles.btnText}>{saving ? 'Saving…' : 'Save'}</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={[styles.btn, styles.btnDiscard]} onPress={handleDiscard}> <TouchableOpacity style={[styles.btn, styles.btnDiscard]} onPress={handleDiscard}>
<Text style={[styles.btnText, { color: '#ef4444' }]}>Discard</Text> <Text style={[styles.btnText, { color: colors.error }]}>Discard</Text>
</TouchableOpacity> </TouchableOpacity>
</ScrollView> </ScrollView>
); );
@@ -91,16 +92,16 @@ function Stat({ label, value }: { label: string; value: string }) {
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#111' }, container: { flex: 1, backgroundColor: colors.bg },
content: { padding: 24, gap: 16 }, content: { padding: 24, gap: 12 },
heading: { color: '#fff', fontSize: 24, fontWeight: '700', marginBottom: 8 }, heading: { color: colors.text, fontSize: 22, fontWeight: '700', marginBottom: 4 },
statsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 12, marginBottom: 8 }, statsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginBottom: 4 },
stat: { flex: 1, minWidth: '40%', backgroundColor: '#1e1e1e', borderRadius: 12, padding: 16 }, stat: { flex: 1, minWidth: '40%', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14 },
statLabel: { color: '#888', fontSize: 12, textTransform: 'uppercase' }, statLabel: { color: colors.textMuted, fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.5 },
statValue: { color: '#fff', fontSize: 22, fontWeight: '600', marginTop: 4 }, statValue: { color: colors.text, fontSize: 22, fontWeight: '700', marginTop: 4 },
input: { backgroundColor: '#1e1e1e', color: '#fff', borderRadius: 12, padding: 16, fontSize: 18 }, input: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, color: colors.text, borderRadius: 10, padding: 14, fontSize: 16 },
btn: { borderRadius: 12, padding: 16, alignItems: 'center' }, btn: { borderRadius: 10, padding: 16, alignItems: 'center' },
btnSave: { backgroundColor: '#22c55e' }, btnSave: { backgroundColor: colors.btnStart },
btnDiscard: { backgroundColor: '#1e1e1e' }, btnDiscard:{ backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border },
btnText: { fontSize: 16, fontWeight: '700', color: '#fff' }, btnText: { fontSize: 16, fontWeight: '600', color: colors.text },
}); });
+20 -22
View File
@@ -7,12 +7,13 @@ import { Map, Camera, GeoJSONSource, Layer, UserLocation } from '@maplibre/mapli
import type { LineLayerStyle } from '@maplibre/maplibre-react-native'; import type { LineLayerStyle } from '@maplibre/maplibre-react-native';
import { useRecordingStore } from '../store/recording'; import { useRecordingStore } from '../store/recording';
import { startGpsRecording, stopGpsRecording, requestLocationPermissions } from '../services/gps'; import { startGpsRecording, stopGpsRecording, requestLocationPermissions } from '../services/gps';
import { RootStackParamList, TrackPoint } from '../types'; import { RootStackParamList } from '../types';
import { colors } from '../theme';
const MAP_STYLE = 'https://tiles.openfreemap.org/styles/liberty'; const MAP_STYLE = 'https://tiles.openfreemap.org/styles/liberty';
const trackLineStyle: LineLayerStyle = { const trackLineStyle: LineLayerStyle = {
lineColor: '#3b82f6', lineColor: colors.accent,
lineWidth: 3, lineWidth: 3,
lineJoin: 'round', lineJoin: 'round',
lineCap: 'round', lineCap: 'round',
@@ -41,10 +42,7 @@ export function RecordingScreen() {
const trackGeoJSON = useMemo<GeoJSON.Feature<GeoJSON.LineString>>(() => ({ const trackGeoJSON = useMemo<GeoJSON.Feature<GeoJSON.LineString>>(() => ({
type: 'Feature', type: 'Feature',
geometry: { geometry: { type: 'LineString', coordinates: trackPoints.map((p) => [p.lon, p.lat]) },
type: 'LineString',
coordinates: trackPoints.map((p) => [p.lon, p.lat]),
},
properties: {}, properties: {},
}), [trackPoints]); }), [trackPoints]);
@@ -118,7 +116,7 @@ export function RecordingScreen() {
style={[styles.awakeBtn, keepAwake && styles.awakeBtnOn]} style={[styles.awakeBtn, keepAwake && styles.awakeBtnOn]}
onPress={() => setKeepAwake(!keepAwake)} onPress={() => setKeepAwake(!keepAwake)}
> >
<Text style={styles.awakeBtnText}>{keepAwake ? '☀️ Awake' : '💤 Sleep'}</Text> <Text style={styles.awakeBtnText}>{keepAwake ? ' Awake' : ' Sleep'}</Text>
</TouchableOpacity> </TouchableOpacity>
{status === 'idle' && ( {status === 'idle' && (
@@ -161,21 +159,21 @@ function StatBox({ label, value }: { label: string; value: string }) {
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#111' }, container: { flex: 1, backgroundColor: colors.bg },
statsGrid: { flexDirection: 'row', flexWrap: 'wrap', padding: 8 }, statsGrid: { flexDirection: 'row', flexWrap: 'wrap', padding: 8, borderBottomWidth: 1, borderBottomColor: colors.border },
statBox: { width: '25%', padding: 8, alignItems: 'center' }, statBox: { width: '25%', padding: 8, alignItems: 'center' },
statLabel: { color: '#888', fontSize: 11, textTransform: 'uppercase' }, statLabel: { color: colors.textMuted, fontSize: 10, textTransform: 'uppercase', letterSpacing: 0.5 },
statValue: { color: '#fff', fontSize: 18, fontWeight: '600', marginTop: 2 }, statValue: { color: colors.text, fontSize: 17, fontWeight: '600', marginTop: 2 },
mapArea: { flex: 1, overflow: 'hidden' }, mapArea: { flex: 1, overflow: 'hidden' },
sensorBtn: { position: 'absolute', top: 10, right: 10, backgroundColor: 'rgba(17,17,17,0.85)', borderRadius: 8, paddingVertical: 6, paddingHorizontal: 12 }, sensorBtn: { position: 'absolute', top: 10, right: 10, backgroundColor: 'rgba(9,9,11,0.8)', borderRadius: 8, borderWidth: 1, borderColor: colors.border, paddingVertical: 6, paddingHorizontal: 12 },
sensorBtnText: { color: '#3b82f6', fontSize: 13, fontWeight: '600' }, sensorBtnText: { color: colors.accent, fontSize: 13, fontWeight: '600' },
controls: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 12, padding: 20 }, controls: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 12, padding: 20, borderTopWidth: 1, borderTopColor: colors.border },
awakeBtn: { backgroundColor: '#1e1e1e', borderRadius: 20, paddingVertical: 8, paddingHorizontal: 14 }, awakeBtn: { backgroundColor: colors.surface, borderRadius: 20, borderWidth: 1, borderColor: colors.border, paddingVertical: 8, paddingHorizontal: 14 },
awakeBtnOn: { backgroundColor: '#2a2a1a' }, awakeBtnOn: { borderColor: colors.accent },
awakeBtnText: { color: '#aaa', fontSize: 13 }, awakeBtnText: { color: colors.textSub, fontSize: 13 },
btn: { paddingVertical: 16, paddingHorizontal: 32, borderRadius: 50 }, btn: { paddingVertical: 15, paddingHorizontal: 30, borderRadius: 50 },
btnStart: { backgroundColor: '#22c55e' }, btnStart: { backgroundColor: colors.btnStart },
btnPause: { backgroundColor: '#f59e0b' }, btnPause: { backgroundColor: colors.btnPause },
btnStop: { backgroundColor: '#ef4444' }, btnStop: { backgroundColor: colors.btnStop },
btnText: { color: '#fff', fontSize: 18, fontWeight: '700' }, btnText: { color: '#fff', fontSize: 17, fontWeight: '700' },
}); });
+12 -11
View File
@@ -6,6 +6,7 @@ import { listRecordings, deleteRecording } from '../services/db';
import { uploadGpx } from '../services/upload'; import { uploadGpx } from '../services/upload';
import { SavedRecording } from '../types'; import { SavedRecording } from '../types';
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import { colors } from '../theme';
export function SavedRecordingsScreen() { export function SavedRecordingsScreen() {
const [recordings, setRecordings] = useState<SavedRecording[]>([]); const [recordings, setRecordings] = useState<SavedRecording[]>([]);
@@ -26,7 +27,7 @@ export function SavedRecordingsScreen() {
const instanceUrl = await AsyncStorage.getItem('instanceUrl'); const instanceUrl = await AsyncStorage.getItem('instanceUrl');
const apiToken = await AsyncStorage.getItem('apiToken'); const apiToken = await AsyncStorage.getItem('apiToken');
if (!instanceUrl || !apiToken) { if (!instanceUrl || !apiToken) {
Alert.alert('Not configured', 'Set your bincio instance URL and API token in Settings.'); Alert.alert('Not connected', 'Log in to your bincio instance in Settings first.');
return; return;
} }
setUploading(rec.id); setUploading(rec.id);
@@ -56,7 +57,7 @@ export function SavedRecordingsScreen() {
return h > 0 ? `${h}h ${m}m` : `${m}m`; return h > 0 ? `${h}h ${m}m` : `${m}m`;
}; };
if (loading) return <View style={styles.center}><ActivityIndicator color="#fff" /></View>; if (loading) return <View style={styles.center}><ActivityIndicator color={colors.accent} /></View>;
return ( return (
<View style={styles.container}> <View style={styles.container}>
@@ -80,7 +81,7 @@ export function SavedRecordingsScreen() {
<Text style={styles.action}>{uploading === item.id ? '…' : 'Upload'}</Text> <Text style={styles.action}>{uploading === item.id ? '…' : 'Upload'}</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => handleDelete(item)}> <TouchableOpacity onPress={() => handleDelete(item)}>
<Text style={[styles.action, { color: '#ef4444' }]}>Delete</Text> <Text style={[styles.action, { color: colors.error }]}>Delete</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
@@ -92,14 +93,14 @@ export function SavedRecordingsScreen() {
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#111' }, container: { flex: 1, backgroundColor: colors.bg },
center: { flex: 1, backgroundColor: '#111', alignItems: 'center', justifyContent: 'center' }, center: { flex: 1, backgroundColor: colors.bg, alignItems: 'center', justifyContent: 'center' },
list: { padding: 16, gap: 12 }, list: { padding: 16, gap: 10 },
card: { backgroundColor: '#1e1e1e', borderRadius: 12, padding: 16 }, card: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 12, padding: 16 },
cardMeta: { marginBottom: 12 }, cardMeta: { marginBottom: 12 },
cardTitle: { color: '#fff', fontSize: 17, fontWeight: '600' }, cardTitle: { color: colors.text, fontSize: 15, fontWeight: '600' },
cardSub: { color: '#888', fontSize: 13, marginTop: 4 }, cardSub: { color: colors.textMuted, fontSize: 12, marginTop: 4 },
cardActions:{ flexDirection: 'row', gap: 20 }, cardActions:{ flexDirection: 'row', gap: 20 },
action: { color: '#3b82f6', fontWeight: '600', fontSize: 15 }, action: { color: colors.accent, fontWeight: '600', fontSize: 14 },
empty: { color: '#555', textAlign: 'center', marginTop: 60 }, empty: { color: colors.textMuted, textAlign: 'center', marginTop: 60 },
}); });
+25 -25
View File
@@ -8,6 +8,7 @@ import {
subscribeForDevice, savePairedDevice, loadPairedDevices, removePairedDevice, subscribeForDevice, savePairedDevice, loadPairedDevices, removePairedDevice,
} from '../services/ble'; } from '../services/ble';
import { BleDevice } from '../types'; import { BleDevice } from '../types';
import { colors } from '../theme';
const TYPE_LABEL: Record<BleDevice['type'], string> = { const TYPE_LABEL: Record<BleDevice['type'], string> = {
hr: 'Heart Rate', hr: 'Heart Rate',
@@ -29,7 +30,6 @@ export function SensorPairingScreen() {
const [connecting, setConnecting] = useState<Record<string, boolean>>({}); const [connecting, setConnecting] = useState<Record<string, boolean>>({});
const stopScanRef = useRef<(() => void) | null>(null); const stopScanRef = useRef<(() => void) | null>(null);
// Load saved devices and attempt reconnect on mount
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const devices = await loadPairedDevices(); const devices = await loadPairedDevices();
@@ -89,14 +89,14 @@ export function SensorPairingScreen() {
setSaved((prev) => prev.filter((e) => e.device.id !== device.id)); setSaved((prev) => prev.filter((e) => e.device.id !== device.id));
} }
const savedIds = new Set(saved.map((e) => e.device.id));
const newFound = found.filter((d) => !savedIds.has(d.id));
type ListItem = type ListItem =
| { kind: 'header'; label: string } | { kind: 'header'; label: string }
| { kind: 'saved'; entry: SavedEntry } | { kind: 'saved'; entry: SavedEntry }
| { kind: 'found'; device: BleDevice }; | { kind: 'found'; device: BleDevice };
const savedIds = new Set(saved.map((e) => e.device.id));
const newFound = found.filter((d) => !savedIds.has(d.id));
const listData: ListItem[] = [ const listData: ListItem[] = [
...(saved.length > 0 ? [{ kind: 'header' as const, label: 'Saved sensors' }, ...saved.map((e) => ({ kind: 'saved' as const, entry: e }))] : []), ...(saved.length > 0 ? [{ kind: 'header' as const, label: 'Saved sensors' }, ...saved.map((e) => ({ kind: 'saved' as const, entry: e }))] : []),
...(newFound.length > 0 ? [{ kind: 'header' as const, label: 'Found nearby' }, ...newFound.map((d) => ({ kind: 'found' as const, device: d }))] : []), ...(newFound.length > 0 ? [{ kind: 'header' as const, label: 'Found nearby' }, ...newFound.map((d) => ({ kind: 'found' as const, device: d }))] : []),
@@ -104,11 +104,10 @@ export function SensorPairingScreen() {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.heading}>Sensors</Text>
<Text style={styles.sub}>Pair your HR monitor, power meter, or cadence sensor.</Text> <Text style={styles.sub}>Pair your HR monitor, power meter, or cadence sensor.</Text>
<TouchableOpacity style={styles.scanBtn} onPress={handleScan} disabled={scanning}> <TouchableOpacity style={styles.scanBtn} onPress={handleScan} disabled={scanning}>
{scanning ? <ActivityIndicator color="#fff" /> : <Text style={styles.scanBtnText}>Scan for sensors</Text>} {scanning ? <ActivityIndicator color={colors.text} /> : <Text style={styles.scanBtnText}>Scan for sensors</Text>}
</TouchableOpacity> </TouchableOpacity>
<FlatList <FlatList
@@ -128,7 +127,7 @@ export function SensorPairingScreen() {
<Text style={styles.deviceType}>{TYPE_LABEL[entry.device.type]}</Text> <Text style={styles.deviceType}>{TYPE_LABEL[entry.device.type]}</Text>
</View> </View>
<View style={styles.deviceActions}> <View style={styles.deviceActions}>
{entry.status === 'connecting' && <ActivityIndicator color="#3b82f6" />} {entry.status === 'connecting' && <ActivityIndicator color={colors.accent} />}
{entry.status === 'connected' && <Text style={styles.connectedLabel}>Connected</Text>} {entry.status === 'connected' && <Text style={styles.connectedLabel}>Connected</Text>}
{(entry.status === 'saved' || entry.status === 'error') && ( {(entry.status === 'saved' || entry.status === 'error') && (
<TouchableOpacity style={styles.reconnectBtn} onPress={() => reconnect(entry.device, true)}> <TouchableOpacity style={styles.reconnectBtn} onPress={() => reconnect(entry.device, true)}>
@@ -155,7 +154,9 @@ export function SensorPairingScreen() {
onPress={() => handleConnect(device)} onPress={() => handleConnect(device)}
disabled={isConnecting} disabled={isConnecting}
> >
{isConnecting ? <ActivityIndicator color="#fff" /> : <Text style={styles.connectBtnText}>Connect</Text>} {isConnecting
? <ActivityIndicator color={colors.text} />
: <Text style={styles.connectBtnText}>Connect</Text>}
</TouchableOpacity> </TouchableOpacity>
</View> </View>
); );
@@ -169,23 +170,22 @@ export function SensorPairingScreen() {
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#111', padding: 24 }, container: { flex: 1, backgroundColor: colors.bg, padding: 16 },
heading: { color: '#fff', fontSize: 24, fontWeight: '700' }, sub: { color: colors.textMuted, fontSize: 13, marginBottom: 16 },
sub: { color: '#888', marginTop: 4, marginBottom: 20 }, scanBtn: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14, alignItems: 'center', marginBottom: 4 },
scanBtn: { backgroundColor: '#3b82f6', borderRadius: 12, padding: 14, alignItems: 'center', marginBottom: 8 }, scanBtnText: { color: colors.text, fontSize: 15, fontWeight: '600' },
scanBtnText: { color: '#fff', fontSize: 16, fontWeight: '700' }, list: { flex: 1, marginTop: 4 },
list: { flex: 1, marginTop: 8 }, sectionHeader: { color: colors.textMuted, fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 20, marginBottom: 8 },
sectionHeader: { color: '#555', fontSize: 12, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 16, marginBottom: 8 }, deviceRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14, marginBottom: 8 },
deviceRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#1e1e1e', borderRadius: 12, padding: 16, marginBottom: 8 },
deviceInfo: { flex: 1, marginRight: 12 }, deviceInfo: { flex: 1, marginRight: 12 },
deviceName: { color: '#fff', fontSize: 16, fontWeight: '600' }, deviceName: { color: colors.text, fontSize: 15, fontWeight: '600' },
deviceType: { color: '#888', fontSize: 13, marginTop: 2 }, deviceType: { color: colors.textMuted, fontSize: 12, marginTop: 2 },
deviceActions: { flexDirection: 'row', alignItems: 'center', gap: 12 }, deviceActions: { flexDirection: 'row', alignItems: 'center', gap: 12 },
connectedLabel: { color: '#22c55e', fontWeight: '600', fontSize: 14 }, connectedLabel: { color: colors.success, fontWeight: '600', fontSize: 13 },
reconnectBtn: { backgroundColor: '#1e3a5f', borderRadius: 8, paddingVertical: 7, paddingHorizontal: 12 }, reconnectBtn: { backgroundColor: colors.accentDim, borderRadius: 8, paddingVertical: 6, paddingHorizontal: 12 },
reconnectBtnText: { color: '#3b82f6', fontWeight: '600', fontSize: 14 }, reconnectBtnText:{ color: colors.accent, fontWeight: '600', fontSize: 13 },
forgetLabel: { color: '#ef4444', fontSize: 14 }, forgetLabel: { color: colors.error, fontSize: 13 },
connectBtn: { backgroundColor: '#3b82f6', borderRadius: 8, paddingVertical: 8, paddingHorizontal: 16, minWidth: 80, alignItems: 'center' }, connectBtn: { backgroundColor: colors.accentDim, borderRadius: 8, paddingVertical: 7, paddingHorizontal: 14, minWidth: 80, alignItems: 'center' },
connectBtnText: { color: '#fff', fontWeight: '600' }, connectBtnText: { color: colors.accent, fontWeight: '600' },
empty: { color: '#555', textAlign: 'center', marginTop: 40 }, empty: { color: colors.textMuted, textAlign: 'center', marginTop: 40 },
}); });
+27 -21
View File
@@ -5,6 +5,7 @@ import {
} from 'react-native'; } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import { login, logout, loadAuthState } from '../services/auth'; import { login, logout, loadAuthState } from '../services/auth';
import { colors } from '../theme';
export function SettingsScreen() { export function SettingsScreen() {
const [instanceUrl, setInstanceUrl] = useState(''); const [instanceUrl, setInstanceUrl] = useState('');
@@ -77,7 +78,7 @@ export function SettingsScreen() {
value={instanceUrl} value={instanceUrl}
onChangeText={setInstanceUrl} onChangeText={setInstanceUrl}
placeholder="https://bincio.example.com" placeholder="https://bincio.example.com"
placeholderTextColor="#555" placeholderTextColor={colors.placeholder}
autoCapitalize="none" autoCapitalize="none"
keyboardType="url" keyboardType="url"
editable={!connectedAs} editable={!connectedAs}
@@ -101,7 +102,7 @@ export function SettingsScreen() {
value={handle} value={handle}
onChangeText={setHandle} onChangeText={setHandle}
placeholder="your-handle" placeholder="your-handle"
placeholderTextColor="#555" placeholderTextColor={colors.placeholder}
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
/> />
@@ -112,7 +113,7 @@ export function SettingsScreen() {
value={password} value={password}
onChangeText={setPassword} onChangeText={setPassword}
placeholder="••••••••" placeholder="••••••••"
placeholderTextColor="#555" placeholderTextColor={colors.placeholder}
secureTextEntry secureTextEntry
/> />
@@ -126,7 +127,7 @@ export function SettingsScreen() {
disabled={connecting} disabled={connecting}
> >
{connecting {connecting
? <ActivityIndicator color="#fff" /> ? <ActivityIndicator color={colors.text} />
: <Text style={styles.connectBtnText}>Connect</Text>} : <Text style={styles.connectBtnText}>Connect</Text>}
</TouchableOpacity> </TouchableOpacity>
</> </>
@@ -136,27 +137,32 @@ export function SettingsScreen() {
<View style={styles.row}> <View style={styles.row}>
<Text style={styles.rowLabel}>Kilometre alerts</Text> <Text style={styles.rowLabel}>Kilometre alerts</Text>
<Switch value={kmNotifications} onValueChange={handleKmToggle} trackColor={{ true: '#3b82f6' }} /> <Switch
value={kmNotifications}
onValueChange={handleKmToggle}
trackColor={{ true: colors.accent }}
thumbColor={colors.text}
/>
</View> </View>
</ScrollView> </ScrollView>
); );
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#111' }, container: { flex: 1, backgroundColor: colors.bg },
content: { padding: 24, gap: 12 }, content: { padding: 16, gap: 10 },
sectionTitle: { color: '#888', fontSize: 13, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 16 }, sectionTitle: { color: colors.textMuted, fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 16 },
label: { color: '#aaa', fontSize: 14, marginBottom: 4 }, label: { color: colors.textSub, fontSize: 13, marginBottom: 2 },
input: { backgroundColor: '#1e1e1e', color: '#fff', borderRadius: 10, padding: 14, fontSize: 16 }, input: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, color: colors.text, borderRadius: 8, padding: 12, fontSize: 15 },
hint: { color: '#555', fontSize: 13, lineHeight: 18 }, hint: { color: colors.textMuted, fontSize: 12, lineHeight: 18 },
connectedBox: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#1e1e1e', borderRadius: 10, padding: 14 }, connectedBox: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14 },
connectedLabel: { color: '#22c55e', fontSize: 12, textTransform: 'uppercase', letterSpacing: 0.6 }, connectedLabel: { color: colors.success, fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.6 },
connectedName: { color: '#fff', fontSize: 16, fontWeight: '600', marginTop: 2 }, connectedName: { color: colors.text, fontSize: 15, fontWeight: '600', marginTop: 2 },
disconnectBtn: { paddingVertical: 6, paddingHorizontal: 12, borderRadius: 8, borderWidth: 1, borderColor: '#ef4444' }, disconnectBtn: { paddingVertical: 6, paddingHorizontal: 12, borderRadius: 8, borderWidth: 1, borderColor: colors.errorBg },
disconnectBtnText: { color: '#ef4444', fontWeight: '600' }, disconnectBtnText: { color: colors.error, fontWeight: '600', fontSize: 13 },
connectBtn: { backgroundColor: '#3b82f6', borderRadius: 12, padding: 16, alignItems: 'center', marginTop: 4 }, connectBtn: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14, alignItems: 'center', marginTop: 4 },
connectBtnDisabled: { opacity: 0.6 }, connectBtnDisabled: { opacity: 0.5 },
connectBtnText: { color: '#fff', fontSize: 16, fontWeight: '700' }, connectBtnText: { color: colors.text, fontSize: 15, fontWeight: '600' },
row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#1e1e1e', borderRadius: 10, padding: 14 }, row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14 },
rowLabel: { color: '#fff', fontSize: 16 }, rowLabel: { color: colors.text, fontSize: 15 },
}); });
+24
View File
@@ -0,0 +1,24 @@
export const colors = {
bg: '#09090b',
surface: '#18181b',
border: '#27272a',
borderStrong: '#3f3f46',
text: '#f4f4f5',
textSub: '#a1a1aa',
textMuted: '#71717a',
placeholder: '#52525b',
accent: '#60a5fa',
accentDim: 'rgba(96,165,250,0.15)',
success: '#86efac',
successBg: '#14532d',
error: '#fca5a5',
errorBg: '#7f1d1d',
// recording action buttons
btnStart: '#16a34a',
btnPause: '#d97706',
btnStop: '#dc2626',
} as const;