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:
Davide Scaini
2026-06-02 22:16:56 +02:00
parent ee28cb0c30
commit 896b528a4c
18 changed files with 2048 additions and 60 deletions
+84
View File
@@ -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' },
});