import { useSQLiteContext } from 'expo-sqlite';
import { useState } from 'react';
import {
ActivityIndicator, Platform, Pressable, ScrollView, StyleSheet,
Text, TextInput, View,
} from 'react-native';
import { getSetting, setSetting, useSetting } from '@/db/queries';
export default function SettingsScreen() {
const db = useSQLiteContext();
const storedUrl = useSetting('instance_url') ?? '';
const storedHandle = useSetting('handle') ?? '';
const storedPath = useSetting('auto_import_path') ?? '';
const storedToken = useSetting('api_token');
const [instanceUrl, setInstanceUrl] = useState(storedUrl);
const [handle, setHandle] = useState(storedHandle);
const [autoPath, setAutoPath] = useState(storedPath);
const [saved, setSaved] = useState(false);
const [password, setPassword] = useState('');
const [connecting, setConnecting] = useState(false);
const [connectMsg, setConnectMsg] = useState<{ ok: boolean; text: string } | null>(null);
async function save() {
await setSetting(db, 'instance_url', instanceUrl.trim());
await setSetting(db, 'handle', handle.trim());
if (Platform.OS === 'android') {
await setSetting(db, 'auto_import_path', autoPath.trim());
}
setSaved(true);
setTimeout(() => setSaved(false), 2000);
}
async function connect() {
const url = instanceUrl.trim().replace(/\/$/, '');
const h = handle.trim();
if (!url || !h || !password) {
setConnectMsg({ ok: false, text: 'Fill in URL, handle, and password first.' });
return;
}
setConnecting(true);
setConnectMsg(null);
try {
const resp = await fetch(`${url}/api/auth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ handle: h, password }),
});
if (!resp.ok) {
const err = await resp.json().catch(() => ({}));
setConnectMsg({ ok: false, text: err.detail ?? `Error ${resp.status}` });
return;
}
const data = await resp.json();
await setSetting(db, 'instance_url', url);
await setSetting(db, 'handle', h);
await setSetting(db, 'api_token', data.token);
setPassword('');
setConnectMsg({ ok: true, text: `Connected as ${data.display_name || h}` });
} catch {
setConnectMsg({ ok: false, text: 'Could not reach instance — check the URL.' });
} finally {
setConnecting(false);
}
}
async function disconnect() {
await setSetting(db, 'api_token', '');
setConnectMsg(null);
}
const isConnected = !!storedToken;
return (
Settings
Connect to a Bincio instance to sync your activities. Leave blank to use
the app offline only.
{saved ? '✓ Saved' : 'Save'}
{isConnected ? (
<>
Disconnect
>
) : (
<>
{connecting
?
: Connect}
>
)}
{connectMsg && (
{connectMsg.text}
)}
Your password is used once to obtain a session token, then forgotten.
The token is stored locally and sent with each sync request.
{Platform.OS === 'android' && (
New FIT files in this directory are imported automatically in the
background. Leave blank to disable. Requires storage permission.
)}
);
}
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
{title}
{children}
);
}
function Field({
label, placeholder, value, onChangeText, ...rest
}: {
label: string;
placeholder: string;
value: string;
onChangeText: (v: string) => void;
[key: string]: unknown;
}) {
return (
{label}
);
}
function Row({ label, value }: { label: string; value: string }) {
return (
{label}
{value}
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#09090b' },
content: { padding: 16, paddingTop: 60, paddingBottom: 40 },
header: { color: '#fff', fontSize: 22, fontWeight: '700', marginBottom: 24 },
section: { marginBottom: 28 },
sectionTitle: {
color: '#a1a1aa', fontSize: 11, fontWeight: '600',
letterSpacing: 0.8, marginBottom: 8,
},
sectionBody: {
backgroundColor: '#18181b', borderRadius: 10,
borderWidth: 1, borderColor: '#27272a', overflow: 'hidden',
},
field: { padding: 14, borderBottomWidth: 1, borderBottomColor: '#27272a' },
fieldLabel: { color: '#71717a', fontSize: 11, marginBottom: 4 },
input: { color: '#f4f4f5', fontSize: 15 },
hint: { color: '#52525b', fontSize: 12, lineHeight: 16, padding: 12 },
row: {
flexDirection: 'row', justifyContent: 'space-between',
paddingHorizontal: 14, paddingVertical: 12,
borderBottomWidth: 1, borderBottomColor: '#27272a',
},
rowLabel: { color: '#a1a1aa', fontSize: 14 },
rowValue: { color: '#71717a', fontSize: 14 },
saveButton: {
backgroundColor: '#2563eb', borderRadius: 10,
paddingVertical: 14, alignItems: 'center', marginBottom: 28,
},
saveButtonText: { color: '#fff', fontWeight: '600', fontSize: 16 },
connectButton: {
backgroundColor: '#059669', borderRadius: 8, margin: 12,
paddingVertical: 12, alignItems: 'center',
},
connectText: { color: '#fff', fontWeight: '600', fontSize: 15 },
buttonDisabled: { opacity: 0.5 },
disconnectButton: {
margin: 12, paddingVertical: 10, alignItems: 'center',
borderRadius: 8, borderWidth: 1, borderColor: '#3f3f46',
},
disconnectText: { color: '#71717a', fontSize: 14 },
msgOk: { color: '#86efac', fontSize: 13, paddingHorizontal: 12, paddingBottom: 10 },
msgErr: { color: '#fca5a5', fontSize: 13, paddingHorizontal: 12, paddingBottom: 10 },
});