feat(mobile): search/filter tab — sport, date, sort; hidden on Karoo

Adds a fourth tab visible only on Android API 29+ (full phone, not Karoo).
Filters by sport pill, date preset (7d/30d/6mo/year), and sort order
(newest/distance/elevation). Paginated FlatList with the same activity cards.

ActivityCard extracted to mobile/components/ActivityCard.tsx so both the
feed tab and the new search tab share the same component without duplication.
This commit is contained in:
Davide Scaini
2026-04-27 15:10:35 +02:00
parent be772bd3df
commit 090d4bd8dc
5 changed files with 308 additions and 103 deletions
+42
View File
@@ -74,6 +74,48 @@ export function useActivityCount(searchQuery = ''): number {
export { PAGE_SIZE };
export type ActivityFilter = {
sport: string; // '' = all sports
dateFrom: string; // '' = no lower bound; ISO-like 'YYYY-MM-DDTHHMMSSZ' for comparison
sort: 'date' | 'distance' | 'elevation';
};
const SORT_SQL: Record<string, string> = {
date: "json_extract(detail_json, '$.started_at') DESC",
distance: "json_extract(detail_json, '$.distance_m') DESC",
elevation: "json_extract(detail_json, '$.elevation_gain_m') DESC",
};
export function useFilteredActivities(filter: ActivityFilter, limit = PAGE_SIZE): ActivitySummary[] {
const db = useSQLiteContext();
const order = SORT_SQL[filter.sort] ?? SORT_SQL.date;
return db.getAllSync<ActivitySummary>(`
SELECT
id, origin, synced_at,
json_extract(detail_json, '$.title') AS title,
json_extract(detail_json, '$.sport') AS sport,
json_extract(detail_json, '$.started_at') AS started_at,
json_extract(detail_json, '$.distance_m') AS distance_m,
json_extract(detail_json, '$.duration_s') AS duration_s,
json_extract(detail_json, '$.elevation_gain_m') AS elevation_gain_m
FROM activities
WHERE (? = '' OR json_extract(detail_json, '$.sport') = ?)
AND (? = '' OR json_extract(detail_json, '$.started_at') >= ?)
ORDER BY ${order}
LIMIT ?
`, [filter.sport, filter.sport, filter.dateFrom, filter.dateFrom, limit]);
}
export function useFilteredCount(filter: ActivityFilter): number {
const db = useSQLiteContext();
const row = db.getFirstSync<{ n: number }>(`
SELECT COUNT(*) as n FROM activities
WHERE (? = '' OR json_extract(detail_json, '$.sport') = ?)
AND (? = '' OR json_extract(detail_json, '$.started_at') >= ?)
`, [filter.sport, filter.sport, filter.dateFrom, filter.dateFrom]);
return row?.n ?? 0;
}
export function useActivity(id: string): ActivityRow | null {
const db = useSQLiteContext();
return db.getFirstSync<ActivityRow>(