diff --git a/mobile/app/(tabs)/index.tsx b/mobile/app/(tabs)/index.tsx
index da20d17..144cb1b 100644
--- a/mobile/app/(tabs)/index.tsx
+++ b/mobile/app/(tabs)/index.tsx
@@ -20,10 +20,13 @@ export default function FeedScreen() {
setSyncMsg(result.error);
} else if (result.total === 0) {
setSyncMsg('No activities on instance');
- } else if (result.synced === 0) {
+ } else if (result.synced === 0 && !result.fetched) {
setSyncMsg(`Up to date (${result.total} activities)`);
} else {
- setSyncMsg(`${result.synced} of ${result.total} activities synced`);
+ const parts = [];
+ if (result.synced > 0) parts.push(`${result.synced} new`);
+ if (result.fetched) parts.push(`${result.fetched} full dataset${result.fetched === 1 ? '' : 's'}`);
+ setSyncMsg(`Synced: ${parts.join(', ')} (${result.total} total)`);
}
setTimeout(() => setSyncMsg(null), 3500);
}, [db]);
diff --git a/mobile/app/(tabs)/settings.tsx b/mobile/app/(tabs)/settings.tsx
index b672dfc..b564075 100644
--- a/mobile/app/(tabs)/settings.tsx
+++ b/mobile/app/(tabs)/settings.tsx
@@ -9,10 +9,11 @@ import { deleteRemoteActivities, getSetting, setSetting, useSetting } from '@/db
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 storedUrl = useSetting('instance_url') ?? '';
+ const storedHandle = useSetting('handle') ?? '';
+ const storedPath = useSetting('auto_import_path') ?? '';
+ const storedToken = useSetting('api_token');
+ const storedSyncMode = (useSetting('sync_mode') ?? 'summaries') as 'summaries' | 'full';
const [instanceUrl, setInstanceUrl] = useState(storedUrl);
const [handle, setHandle] = useState(storedHandle);
@@ -174,6 +175,26 @@ export default function SettingsScreen() {
)}
+
+
+ setSetting(db, 'sync_mode', 'summaries')}
+ />
+ setSetting(db, 'sync_mode', 'full')}
+ />
+
+
+ {storedSyncMode === 'full'
+ ? 'Downloads map route and elevation chart for every activity during sync. Uses more storage and takes longer.'
+ : 'Syncs activity summaries only. Map and chart are fetched on demand when you open an activity.'}
+
+
+
void }) {
+ return (
+
+ {label}
+
+ );
+}
+
function Row({ label, value }: { label: string; value: string }) {
return (
@@ -282,6 +314,11 @@ const styles = StyleSheet.create({
disconnectText: { color: '#71717a', fontSize: 14 },
msgOk: { color: '#86efac', fontSize: 13, paddingHorizontal: 12, paddingBottom: 10 },
msgErr: { color: '#fca5a5', fontSize: 13, paddingHorizontal: 12, paddingBottom: 10 },
+ modeRow: { flexDirection: 'row', gap: 8, padding: 12 },
+ modeButton: { flex: 1, paddingVertical: 9, borderRadius: 8, borderWidth: 1, borderColor: '#3f3f46', alignItems: 'center' },
+ modeButtonActive: { backgroundColor: '#1e3a5f', borderColor: '#2563eb' },
+ modeButtonText: { color: '#71717a', fontSize: 13, fontWeight: '500' },
+ modeButtonTextActive: { color: '#60a5fa' },
resetButton: {
margin: 12, paddingVertical: 10, alignItems: 'center',
borderRadius: 8, borderWidth: 1, borderColor: '#3f3f46',
diff --git a/mobile/db/sync.ts b/mobile/db/sync.ts
index b5551d2..2e46e3a 100644
--- a/mobile/db/sync.ts
+++ b/mobile/db/sync.ts
@@ -1,7 +1,7 @@
import type { SQLiteDatabase } from 'expo-sqlite';
import { getSetting, upsertRemoteActivity } from './queries';
-export type SyncResult = { synced: number; total: number; error?: string };
+export type SyncResult = { synced: number; total: number; fetched?: number; error?: string };
export async function syncFeed(db: SQLiteDatabase): Promise {
const instanceUrl = (await getSetting(db, 'instance_url'))?.replace(/\/$/, '');
@@ -30,6 +30,8 @@ export async function syncFeed(db: SQLiteDatabase): Promise {
const data: { activities?: RemoteSummary[] } = await resp.json();
const activities = data.activities ?? [];
+ const syncMode = (await getSetting(db, 'sync_mode')) ?? 'summaries';
+
let synced = 0;
for (const a of activities) {
const detailJson = JSON.stringify({
@@ -48,7 +50,46 @@ export async function syncFeed(db: SQLiteDatabase): Promise {
if (changed) synced++;
}
- return { synced, total: activities.length };
+ if (syncMode !== 'full') {
+ return { synced, total: activities.length };
+ }
+
+ // Full mode: fetch geojson + timeseries for any activity missing them
+ const headers = { Authorization: `Bearer ${token}` };
+ let fetched = 0;
+ for (const a of activities) {
+ const row = db.getFirstSync<{ g: number; t: number }>(
+ 'SELECT (geojson IS NOT NULL) as g, (timeseries_json IS NOT NULL) as t FROM activities WHERE id = ?',
+ [a.id],
+ );
+ if (row?.g && row?.t) continue;
+
+ let gj: string | null = null;
+ let ts: string | null = null;
+ try {
+ if (!row?.g) {
+ const r = await fetch(`${instanceUrl}/api/activity/${a.id}/geojson`, { headers });
+ if (r.ok) gj = await r.text();
+ }
+ if (!row?.t) {
+ const r = await fetch(`${instanceUrl}/api/activity/${a.id}/timeseries`, { headers });
+ if (r.ok) ts = await r.text();
+ }
+ } catch {}
+
+ if (gj !== null || ts !== null) {
+ await db.runAsync(
+ `UPDATE activities SET
+ geojson = COALESCE(geojson, ?),
+ timeseries_json = COALESCE(timeseries_json, ?)
+ WHERE id = ? AND origin = 'remote'`,
+ [gj, ts, a.id],
+ );
+ fetched++;
+ }
+ }
+
+ return { synced, total: activities.length, fetched };
}
type RemoteSummary = {