feat: add delete button for local activities (single and bulk)

- Detail screen: Delete button (top-right, red) with confirmation alert;
  deletes SQLite row and original file via expo-file-system
- Feed screen: long-press card to enter select mode; checkbox + blue
  border on selected cards; bottom action bar with bulk Delete N button;
  header switches to show count + Cancel
- db/queries: deleteActivity (returns original_path) and deleteActivities
  (bulk, returns all original paths)
This commit is contained in:
Davide Scaini
2026-04-25 13:43:12 +02:00
parent c077fceba6
commit 1ac35c84e0
3 changed files with 197 additions and 21 deletions
+38 -6
View File
@@ -1,9 +1,11 @@
import { Camera, GeoJSONSource, Layer, Map } from '@maplibre/maplibre-react-native';
import * as FileSystem from 'expo-file-system';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { useEffect, useState } from 'react';
import { ActivityIndicator, Modal, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { ActivityIndicator, Alert, Modal, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import Svg, { Defs, LinearGradient, Path, Stop } from 'react-native-svg';
import { useActivity, useSetting } from '@/db/queries';
import { useSQLiteContext } from 'expo-sqlite';
import { deleteActivity, useActivity, useSetting } from '@/db/queries';
const MAP_STYLE = 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json';
@@ -25,6 +27,7 @@ type Timeseries = {
export default function ActivityScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const router = useRouter();
const db = useSQLiteContext();
const row = useActivity(id);
const instanceUrl = useSetting('instance_url')?.replace(/\/$/, '') ?? '';
const token = useSetting('api_token') ?? '';
@@ -34,6 +37,27 @@ export default function ActivityScreen() {
const [loadingMap, setLoadingMap] = useState(false);
const [loadingChart, setLoadingChart] = useState(false);
async function confirmDelete() {
Alert.alert(
'Delete activity',
'This will permanently remove this activity from your device.',
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Delete',
style: 'destructive',
onPress: async () => {
const originalPath = await deleteActivity(db, id);
if (originalPath) {
try { await FileSystem.deleteAsync(originalPath, { idempotent: true }); } catch {}
}
router.back();
},
},
],
);
}
// instanceUrl and token are in the dep array to avoid a stale-closure bug in
// release builds: Hermes executes effects sooner and captures empty strings if
// the deps are omitted. Guards on geojson/timeseries prevent double-fetching.
@@ -90,9 +114,14 @@ export default function ActivityScreen() {
return (
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
<Pressable style={styles.backButton} onPress={() => router.back()}>
<Text style={styles.backText}> Back</Text>
</Pressable>
<View style={styles.topBar}>
<Pressable style={styles.backButton} onPress={() => router.back()}>
<Text style={styles.backText}> Back</Text>
</Pressable>
<Pressable style={styles.deleteButton} onPress={confirmDelete}>
<Text style={styles.deleteText}>Delete</Text>
</Pressable>
</View>
<Text style={styles.sport}>{detail.sport ?? 'Activity'}</Text>
<Text style={styles.title}>{detail.title}</Text>
@@ -367,8 +396,11 @@ const styles = StyleSheet.create({
content: { paddingBottom: 40 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: '#09090b' },
notFound: { color: '#71717a', fontSize: 16 },
backButton: { paddingHorizontal: 16, paddingTop: 60, paddingBottom: 12 },
topBar: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingTop: 60, paddingBottom: 12 },
backButton: { paddingHorizontal: 16 },
backText: { color: '#60a5fa', fontSize: 15 },
deleteButton: { paddingHorizontal: 16 },
deleteText: { color: '#f87171', fontSize: 15 },
sport: { color: '#71717a', fontSize: 12, fontWeight: '600', letterSpacing: 0.8, paddingHorizontal: 16, marginBottom: 4 },
title: { color: '#f4f4f5', fontSize: 22, fontWeight: '700', paddingHorizontal: 16, marginBottom: 4 },
date: { color: '#71717a', fontSize: 13, paddingHorizontal: 16, marginBottom: 16 },