feat: scaffold Expo Prebuild project with all v1 screens and services
Sets up the full bincio-rec source tree: Zustand recording store with haversine stats, background GPS via expo-task-manager, BLE scan/subscribe for HR and power, GPX writer with Garmin extensions, SQLite recordings list, multipart upload to bincio-activity, React Navigation stack with bottom tabs, and build instructions in README.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, Text, TextInput, StyleSheet, Switch, TouchableOpacity, Alert, ScrollView } from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
export function SettingsScreen() {
|
||||
const [instanceUrl, setInstanceUrl] = useState('');
|
||||
const [apiToken, setApiToken] = useState('');
|
||||
const [kmNotifications, setKmNotifications] = useState(true);
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const [url, token, km] = await Promise.all([
|
||||
AsyncStorage.getItem('instanceUrl'),
|
||||
AsyncStorage.getItem('apiToken'),
|
||||
AsyncStorage.getItem('kmNotifications'),
|
||||
]);
|
||||
if (url) setInstanceUrl(url);
|
||||
if (token) setApiToken(token);
|
||||
if (km !== null) setKmNotifications(km === 'true');
|
||||
})();
|
||||
}, []);
|
||||
|
||||
async function handleSave() {
|
||||
await Promise.all([
|
||||
AsyncStorage.setItem('instanceUrl', instanceUrl.trim()),
|
||||
AsyncStorage.setItem('apiToken', apiToken.trim()),
|
||||
AsyncStorage.setItem('kmNotifications', String(kmNotifications)),
|
||||
]);
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 2000);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
||||
<Text style={styles.sectionTitle}>bincio instance</Text>
|
||||
|
||||
<Text style={styles.label}>Instance URL</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={instanceUrl}
|
||||
onChangeText={setInstanceUrl}
|
||||
placeholder="https://bincio.example.com"
|
||||
placeholderTextColor="#555"
|
||||
autoCapitalize="none"
|
||||
keyboardType="url"
|
||||
/>
|
||||
|
||||
<Text style={styles.label}>API Token</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={apiToken}
|
||||
onChangeText={setApiToken}
|
||||
placeholder="your-api-token"
|
||||
placeholderTextColor="#555"
|
||||
autoCapitalize="none"
|
||||
secureTextEntry
|
||||
/>
|
||||
|
||||
<Text style={styles.sectionTitle}>Notifications</Text>
|
||||
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.rowLabel}>Kilometre alerts</Text>
|
||||
<Switch value={kmNotifications} onValueChange={setKmNotifications} trackColor={{ true: '#3b82f6' }} />
|
||||
</View>
|
||||
|
||||
<TouchableOpacity style={styles.saveBtn} onPress={handleSave}>
|
||||
<Text style={styles.saveBtnText}>{saved ? 'Saved ✓' : 'Save'}</Text>
|
||||
</TouchableOpacity>
|
||||
</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 },
|
||||
row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#1e1e1e', borderRadius: 10, padding: 14 },
|
||||
rowLabel: { color: '#fff', fontSize: 16 },
|
||||
saveBtn: { backgroundColor: '#3b82f6', borderRadius: 12, padding: 16, alignItems: 'center', marginTop: 8 },
|
||||
saveBtnText: { color: '#fff', fontSize: 16, fontWeight: '700' },
|
||||
});
|
||||
Reference in New Issue
Block a user