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:
@@ -10,36 +10,34 @@ import { SensorPairingScreen } from '../screens/SensorPairingScreen';
|
||||
import { SavedRecordingsScreen } from '../screens/SavedRecordingsScreen';
|
||||
import { SettingsScreen } from '../screens/SettingsScreen';
|
||||
import { RootStackParamList, TabParamList } from '../types';
|
||||
import { colors } from '../theme';
|
||||
|
||||
const Stack = createNativeStackNavigator<RootStackParamList>();
|
||||
const Tab = createBottomTabNavigator<TabParamList>();
|
||||
|
||||
const TAB_ICONS: Record<string, string> = {
|
||||
Recording: '◉',
|
||||
Saved: '☰',
|
||||
Settings: '⚙',
|
||||
};
|
||||
|
||||
function Tabs() {
|
||||
return (
|
||||
<Tab.Navigator
|
||||
screenOptions={{
|
||||
headerStyle: { backgroundColor: '#111' },
|
||||
headerTintColor: '#fff',
|
||||
tabBarStyle: { backgroundColor: '#111', borderTopColor: '#222' },
|
||||
tabBarActiveTintColor: '#3b82f6',
|
||||
tabBarInactiveTintColor: '#555',
|
||||
}}
|
||||
screenOptions={({ route }) => ({
|
||||
headerStyle: { backgroundColor: colors.bg },
|
||||
headerTintColor: colors.text,
|
||||
tabBarStyle: { backgroundColor: colors.surface, borderTopColor: colors.border },
|
||||
tabBarActiveTintColor: colors.accent,
|
||||
tabBarInactiveTintColor: colors.textMuted,
|
||||
tabBarIcon: ({ color }) => (
|
||||
<Text style={{ color, fontSize: 18 }}>{TAB_ICONS[route.name]}</Text>
|
||||
),
|
||||
})}
|
||||
>
|
||||
<Tab.Screen
|
||||
name="Recording"
|
||||
component={RecordingScreen}
|
||||
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.Screen name="Recording" component={RecordingScreen} />
|
||||
<Tab.Screen name="Saved" component={SavedRecordingsScreen} />
|
||||
<Tab.Screen name="Settings" component={SettingsScreen} />
|
||||
</Tab.Navigator>
|
||||
);
|
||||
}
|
||||
@@ -49,9 +47,9 @@ export function AppNavigator() {
|
||||
<NavigationContainer>
|
||||
<Stack.Navigator
|
||||
screenOptions={{
|
||||
headerStyle: { backgroundColor: '#111' },
|
||||
headerTintColor: '#fff',
|
||||
contentStyle: { backgroundColor: '#111' },
|
||||
headerStyle: { backgroundColor: colors.bg },
|
||||
headerTintColor: colors.text,
|
||||
contentStyle: { backgroundColor: colors.bg },
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="Tabs" component={Tabs} options={{ headerShown: false }} />
|
||||
|
||||
@@ -6,6 +6,7 @@ import { saveGpx } from '../services/gpx';
|
||||
import { insertRecording } from '../services/db';
|
||||
import { SavedRecording } from '../types';
|
||||
import { randomUUID } from 'expo-crypto';
|
||||
import { colors } from '../theme';
|
||||
|
||||
export function PostRecordingScreen() {
|
||||
const nav = useNavigation();
|
||||
@@ -65,7 +66,7 @@ export function PostRecordingScreen() {
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Activity title"
|
||||
placeholderTextColor="#555"
|
||||
placeholderTextColor={colors.placeholder}
|
||||
value={title}
|
||||
onChangeText={setTitle}
|
||||
autoFocus
|
||||
@@ -75,7 +76,7 @@ export function PostRecordingScreen() {
|
||||
<Text style={styles.btnText}>{saving ? 'Saving…' : 'Save'}</Text>
|
||||
</TouchableOpacity>
|
||||
<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>
|
||||
</ScrollView>
|
||||
);
|
||||
@@ -91,16 +92,16 @@ function Stat({ label, value }: { label: string; value: string }) {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#111' },
|
||||
content: { padding: 24, gap: 16 },
|
||||
heading: { color: '#fff', fontSize: 24, fontWeight: '700', marginBottom: 8 },
|
||||
statsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 12, marginBottom: 8 },
|
||||
stat: { flex: 1, minWidth: '40%', backgroundColor: '#1e1e1e', borderRadius: 12, padding: 16 },
|
||||
statLabel: { color: '#888', fontSize: 12, textTransform: 'uppercase' },
|
||||
statValue: { color: '#fff', fontSize: 22, fontWeight: '600', marginTop: 4 },
|
||||
input: { backgroundColor: '#1e1e1e', color: '#fff', borderRadius: 12, padding: 16, fontSize: 18 },
|
||||
btn: { borderRadius: 12, padding: 16, alignItems: 'center' },
|
||||
btnSave: { backgroundColor: '#22c55e' },
|
||||
btnDiscard: { backgroundColor: '#1e1e1e' },
|
||||
btnText: { fontSize: 16, fontWeight: '700', color: '#fff' },
|
||||
container: { flex: 1, backgroundColor: colors.bg },
|
||||
content: { padding: 24, gap: 12 },
|
||||
heading: { color: colors.text, fontSize: 22, fontWeight: '700', marginBottom: 4 },
|
||||
statsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginBottom: 4 },
|
||||
stat: { flex: 1, minWidth: '40%', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14 },
|
||||
statLabel: { color: colors.textMuted, fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.5 },
|
||||
statValue: { color: colors.text, fontSize: 22, fontWeight: '700', marginTop: 4 },
|
||||
input: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, color: colors.text, borderRadius: 10, padding: 14, fontSize: 16 },
|
||||
btn: { borderRadius: 10, padding: 16, alignItems: 'center' },
|
||||
btnSave: { backgroundColor: colors.btnStart },
|
||||
btnDiscard:{ backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border },
|
||||
btnText: { fontSize: 16, fontWeight: '600', color: colors.text },
|
||||
});
|
||||
|
||||
@@ -7,12 +7,13 @@ import { Map, Camera, GeoJSONSource, Layer, UserLocation } from '@maplibre/mapli
|
||||
import type { LineLayerStyle } from '@maplibre/maplibre-react-native';
|
||||
import { useRecordingStore } from '../store/recording';
|
||||
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 trackLineStyle: LineLayerStyle = {
|
||||
lineColor: '#3b82f6',
|
||||
lineColor: colors.accent,
|
||||
lineWidth: 3,
|
||||
lineJoin: 'round',
|
||||
lineCap: 'round',
|
||||
@@ -41,10 +42,7 @@ export function RecordingScreen() {
|
||||
|
||||
const trackGeoJSON = useMemo<GeoJSON.Feature<GeoJSON.LineString>>(() => ({
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: trackPoints.map((p) => [p.lon, p.lat]),
|
||||
},
|
||||
geometry: { type: 'LineString', coordinates: trackPoints.map((p) => [p.lon, p.lat]) },
|
||||
properties: {},
|
||||
}), [trackPoints]);
|
||||
|
||||
@@ -118,7 +116,7 @@ export function RecordingScreen() {
|
||||
style={[styles.awakeBtn, keepAwake && styles.awakeBtnOn]}
|
||||
onPress={() => setKeepAwake(!keepAwake)}
|
||||
>
|
||||
<Text style={styles.awakeBtnText}>{keepAwake ? '☀️ Awake' : '💤 Sleep'}</Text>
|
||||
<Text style={styles.awakeBtnText}>{keepAwake ? '◑ Awake' : '◌ Sleep'}</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{status === 'idle' && (
|
||||
@@ -161,21 +159,21 @@ function StatBox({ label, value }: { label: string; value: string }) {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#111' },
|
||||
statsGrid: { flexDirection: 'row', flexWrap: 'wrap', padding: 8 },
|
||||
container: { flex: 1, backgroundColor: colors.bg },
|
||||
statsGrid: { flexDirection: 'row', flexWrap: 'wrap', padding: 8, borderBottomWidth: 1, borderBottomColor: colors.border },
|
||||
statBox: { width: '25%', padding: 8, alignItems: 'center' },
|
||||
statLabel: { color: '#888', fontSize: 11, textTransform: 'uppercase' },
|
||||
statValue: { color: '#fff', fontSize: 18, fontWeight: '600', marginTop: 2 },
|
||||
statLabel: { color: colors.textMuted, fontSize: 10, textTransform: 'uppercase', letterSpacing: 0.5 },
|
||||
statValue: { color: colors.text, fontSize: 17, fontWeight: '600', marginTop: 2 },
|
||||
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 },
|
||||
sensorBtnText: { color: '#3b82f6', fontSize: 13, fontWeight: '600' },
|
||||
controls: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 12, padding: 20 },
|
||||
awakeBtn: { backgroundColor: '#1e1e1e', borderRadius: 20, paddingVertical: 8, paddingHorizontal: 14 },
|
||||
awakeBtnOn: { backgroundColor: '#2a2a1a' },
|
||||
awakeBtnText: { color: '#aaa', fontSize: 13 },
|
||||
btn: { paddingVertical: 16, paddingHorizontal: 32, borderRadius: 50 },
|
||||
btnStart: { backgroundColor: '#22c55e' },
|
||||
btnPause: { backgroundColor: '#f59e0b' },
|
||||
btnStop: { backgroundColor: '#ef4444' },
|
||||
btnText: { color: '#fff', fontSize: 18, fontWeight: '700' },
|
||||
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: colors.accent, fontSize: 13, fontWeight: '600' },
|
||||
controls: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 12, padding: 20, borderTopWidth: 1, borderTopColor: colors.border },
|
||||
awakeBtn: { backgroundColor: colors.surface, borderRadius: 20, borderWidth: 1, borderColor: colors.border, paddingVertical: 8, paddingHorizontal: 14 },
|
||||
awakeBtnOn: { borderColor: colors.accent },
|
||||
awakeBtnText: { color: colors.textSub, fontSize: 13 },
|
||||
btn: { paddingVertical: 15, paddingHorizontal: 30, borderRadius: 50 },
|
||||
btnStart: { backgroundColor: colors.btnStart },
|
||||
btnPause: { backgroundColor: colors.btnPause },
|
||||
btnStop: { backgroundColor: colors.btnStop },
|
||||
btnText: { color: '#fff', fontSize: 17, fontWeight: '700' },
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { listRecordings, deleteRecording } from '../services/db';
|
||||
import { uploadGpx } from '../services/upload';
|
||||
import { SavedRecording } from '../types';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { colors } from '../theme';
|
||||
|
||||
export function SavedRecordingsScreen() {
|
||||
const [recordings, setRecordings] = useState<SavedRecording[]>([]);
|
||||
@@ -26,7 +27,7 @@ export function SavedRecordingsScreen() {
|
||||
const instanceUrl = await AsyncStorage.getItem('instanceUrl');
|
||||
const apiToken = await AsyncStorage.getItem('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;
|
||||
}
|
||||
setUploading(rec.id);
|
||||
@@ -56,7 +57,7 @@ export function SavedRecordingsScreen() {
|
||||
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 (
|
||||
<View style={styles.container}>
|
||||
@@ -80,7 +81,7 @@ export function SavedRecordingsScreen() {
|
||||
<Text style={styles.action}>{uploading === item.id ? '…' : 'Upload'}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => handleDelete(item)}>
|
||||
<Text style={[styles.action, { color: '#ef4444' }]}>Delete</Text>
|
||||
<Text style={[styles.action, { color: colors.error }]}>Delete</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
@@ -92,14 +93,14 @@ export function SavedRecordingsScreen() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#111' },
|
||||
center: { flex: 1, backgroundColor: '#111', alignItems: 'center', justifyContent: 'center' },
|
||||
list: { padding: 16, gap: 12 },
|
||||
card: { backgroundColor: '#1e1e1e', borderRadius: 12, padding: 16 },
|
||||
container: { flex: 1, backgroundColor: colors.bg },
|
||||
center: { flex: 1, backgroundColor: colors.bg, alignItems: 'center', justifyContent: 'center' },
|
||||
list: { padding: 16, gap: 10 },
|
||||
card: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 12, padding: 16 },
|
||||
cardMeta: { marginBottom: 12 },
|
||||
cardTitle: { color: '#fff', fontSize: 17, fontWeight: '600' },
|
||||
cardSub: { color: '#888', fontSize: 13, marginTop: 4 },
|
||||
cardTitle: { color: colors.text, fontSize: 15, fontWeight: '600' },
|
||||
cardSub: { color: colors.textMuted, fontSize: 12, marginTop: 4 },
|
||||
cardActions:{ flexDirection: 'row', gap: 20 },
|
||||
action: { color: '#3b82f6', fontWeight: '600', fontSize: 15 },
|
||||
empty: { color: '#555', textAlign: 'center', marginTop: 60 },
|
||||
action: { color: colors.accent, fontWeight: '600', fontSize: 14 },
|
||||
empty: { color: colors.textMuted, textAlign: 'center', marginTop: 60 },
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
subscribeForDevice, savePairedDevice, loadPairedDevices, removePairedDevice,
|
||||
} from '../services/ble';
|
||||
import { BleDevice } from '../types';
|
||||
import { colors } from '../theme';
|
||||
|
||||
const TYPE_LABEL: Record<BleDevice['type'], string> = {
|
||||
hr: 'Heart Rate',
|
||||
@@ -29,7 +30,6 @@ export function SensorPairingScreen() {
|
||||
const [connecting, setConnecting] = useState<Record<string, boolean>>({});
|
||||
const stopScanRef = useRef<(() => void) | null>(null);
|
||||
|
||||
// Load saved devices and attempt reconnect on mount
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const devices = await loadPairedDevices();
|
||||
@@ -89,14 +89,14 @@ export function SensorPairingScreen() {
|
||||
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 =
|
||||
| { kind: 'header'; label: string }
|
||||
| { kind: 'saved'; entry: SavedEntry }
|
||||
| { 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[] = [
|
||||
...(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 }))] : []),
|
||||
@@ -104,11 +104,10 @@ export function SensorPairingScreen() {
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.heading}>Sensors</Text>
|
||||
<Text style={styles.sub}>Pair your HR monitor, power meter, or cadence sensor.</Text>
|
||||
|
||||
<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>
|
||||
|
||||
<FlatList
|
||||
@@ -128,7 +127,7 @@ export function SensorPairingScreen() {
|
||||
<Text style={styles.deviceType}>{TYPE_LABEL[entry.device.type]}</Text>
|
||||
</View>
|
||||
<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 === 'saved' || entry.status === 'error') && (
|
||||
<TouchableOpacity style={styles.reconnectBtn} onPress={() => reconnect(entry.device, true)}>
|
||||
@@ -155,7 +154,9 @@ export function SensorPairingScreen() {
|
||||
onPress={() => handleConnect(device)}
|
||||
disabled={isConnecting}
|
||||
>
|
||||
{isConnecting ? <ActivityIndicator color="#fff" /> : <Text style={styles.connectBtnText}>Connect</Text>}
|
||||
{isConnecting
|
||||
? <ActivityIndicator color={colors.text} />
|
||||
: <Text style={styles.connectBtnText}>Connect</Text>}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
@@ -169,23 +170,22 @@ export function SensorPairingScreen() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#111', padding: 24 },
|
||||
heading: { color: '#fff', fontSize: 24, fontWeight: '700' },
|
||||
sub: { color: '#888', marginTop: 4, marginBottom: 20 },
|
||||
scanBtn: { backgroundColor: '#3b82f6', borderRadius: 12, padding: 14, alignItems: 'center', marginBottom: 8 },
|
||||
scanBtnText: { color: '#fff', fontSize: 16, fontWeight: '700' },
|
||||
list: { flex: 1, marginTop: 8 },
|
||||
sectionHeader: { color: '#555', fontSize: 12, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 16, marginBottom: 8 },
|
||||
deviceRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#1e1e1e', borderRadius: 12, padding: 16, marginBottom: 8 },
|
||||
container: { flex: 1, backgroundColor: colors.bg, padding: 16 },
|
||||
sub: { color: colors.textMuted, fontSize: 13, marginBottom: 16 },
|
||||
scanBtn: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14, alignItems: 'center', marginBottom: 4 },
|
||||
scanBtnText: { color: colors.text, fontSize: 15, fontWeight: '600' },
|
||||
list: { flex: 1, marginTop: 4 },
|
||||
sectionHeader: { color: colors.textMuted, fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 20, marginBottom: 8 },
|
||||
deviceRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14, marginBottom: 8 },
|
||||
deviceInfo: { flex: 1, marginRight: 12 },
|
||||
deviceName: { color: '#fff', fontSize: 16, fontWeight: '600' },
|
||||
deviceType: { color: '#888', fontSize: 13, marginTop: 2 },
|
||||
deviceName: { color: colors.text, fontSize: 15, fontWeight: '600' },
|
||||
deviceType: { color: colors.textMuted, fontSize: 12, marginTop: 2 },
|
||||
deviceActions: { flexDirection: 'row', alignItems: 'center', gap: 12 },
|
||||
connectedLabel: { color: '#22c55e', fontWeight: '600', fontSize: 14 },
|
||||
reconnectBtn: { backgroundColor: '#1e3a5f', borderRadius: 8, paddingVertical: 7, paddingHorizontal: 12 },
|
||||
reconnectBtnText: { color: '#3b82f6', fontWeight: '600', fontSize: 14 },
|
||||
forgetLabel: { color: '#ef4444', fontSize: 14 },
|
||||
connectBtn: { backgroundColor: '#3b82f6', borderRadius: 8, paddingVertical: 8, paddingHorizontal: 16, minWidth: 80, alignItems: 'center' },
|
||||
connectBtnText: { color: '#fff', fontWeight: '600' },
|
||||
empty: { color: '#555', textAlign: 'center', marginTop: 40 },
|
||||
connectedLabel: { color: colors.success, fontWeight: '600', fontSize: 13 },
|
||||
reconnectBtn: { backgroundColor: colors.accentDim, borderRadius: 8, paddingVertical: 6, paddingHorizontal: 12 },
|
||||
reconnectBtnText:{ color: colors.accent, fontWeight: '600', fontSize: 13 },
|
||||
forgetLabel: { color: colors.error, fontSize: 13 },
|
||||
connectBtn: { backgroundColor: colors.accentDim, borderRadius: 8, paddingVertical: 7, paddingHorizontal: 14, minWidth: 80, alignItems: 'center' },
|
||||
connectBtnText: { color: colors.accent, fontWeight: '600' },
|
||||
empty: { color: colors.textMuted, textAlign: 'center', marginTop: 40 },
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { login, logout, loadAuthState } from '../services/auth';
|
||||
import { colors } from '../theme';
|
||||
|
||||
export function SettingsScreen() {
|
||||
const [instanceUrl, setInstanceUrl] = useState('');
|
||||
@@ -77,7 +78,7 @@ export function SettingsScreen() {
|
||||
value={instanceUrl}
|
||||
onChangeText={setInstanceUrl}
|
||||
placeholder="https://bincio.example.com"
|
||||
placeholderTextColor="#555"
|
||||
placeholderTextColor={colors.placeholder}
|
||||
autoCapitalize="none"
|
||||
keyboardType="url"
|
||||
editable={!connectedAs}
|
||||
@@ -101,7 +102,7 @@ export function SettingsScreen() {
|
||||
value={handle}
|
||||
onChangeText={setHandle}
|
||||
placeholder="your-handle"
|
||||
placeholderTextColor="#555"
|
||||
placeholderTextColor={colors.placeholder}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
/>
|
||||
@@ -112,7 +113,7 @@ export function SettingsScreen() {
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
placeholder="••••••••"
|
||||
placeholderTextColor="#555"
|
||||
placeholderTextColor={colors.placeholder}
|
||||
secureTextEntry
|
||||
/>
|
||||
|
||||
@@ -126,7 +127,7 @@ export function SettingsScreen() {
|
||||
disabled={connecting}
|
||||
>
|
||||
{connecting
|
||||
? <ActivityIndicator color="#fff" />
|
||||
? <ActivityIndicator color={colors.text} />
|
||||
: <Text style={styles.connectBtnText}>Connect</Text>}
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
@@ -136,27 +137,32 @@ export function SettingsScreen() {
|
||||
|
||||
<View style={styles.row}>
|
||||
<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>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#111' },
|
||||
content: { padding: 24, gap: 12 },
|
||||
sectionTitle: { color: '#888', fontSize: 13, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 16 },
|
||||
label: { color: '#aaa', fontSize: 14, marginBottom: 4 },
|
||||
input: { backgroundColor: '#1e1e1e', color: '#fff', borderRadius: 10, padding: 14, fontSize: 16 },
|
||||
hint: { color: '#555', fontSize: 13, lineHeight: 18 },
|
||||
connectedBox: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#1e1e1e', borderRadius: 10, padding: 14 },
|
||||
connectedLabel: { color: '#22c55e', fontSize: 12, textTransform: 'uppercase', letterSpacing: 0.6 },
|
||||
connectedName: { color: '#fff', fontSize: 16, fontWeight: '600', marginTop: 2 },
|
||||
disconnectBtn: { paddingVertical: 6, paddingHorizontal: 12, borderRadius: 8, borderWidth: 1, borderColor: '#ef4444' },
|
||||
disconnectBtnText: { color: '#ef4444', fontWeight: '600' },
|
||||
connectBtn: { backgroundColor: '#3b82f6', borderRadius: 12, padding: 16, alignItems: 'center', marginTop: 4 },
|
||||
connectBtnDisabled: { opacity: 0.6 },
|
||||
connectBtnText: { color: '#fff', fontSize: 16, fontWeight: '700' },
|
||||
row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#1e1e1e', borderRadius: 10, padding: 14 },
|
||||
rowLabel: { color: '#fff', fontSize: 16 },
|
||||
container: { flex: 1, backgroundColor: colors.bg },
|
||||
content: { padding: 16, gap: 10 },
|
||||
sectionTitle: { color: colors.textMuted, fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 16 },
|
||||
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 },
|
||||
connectedBox: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14 },
|
||||
connectedLabel: { color: colors.success, 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' },
|
||||
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 },
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user