From 0d03f34371a498ba3511cd734e7aa81c0ba1ce80 Mon Sep 17 00:00:00 2001 From: Davide Scaini Date: Thu, 4 Jun 2026 00:57:23 +0200 Subject: [PATCH] feat: offline map download via MapLibre OfflineManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New src/services/offline.ts: - downloadRegion(): createPack with bounds, zoom 6-16, progress callback - listRegions() / deleteRegion(): pack management - expandBounds(): adds 5km buffer around the visible area - formatBytes(): human-readable size string RecordingScreen: - MapRef attached to Map component to read getBounds() - '↓ Offline' overlay button (Liberty style + idle state only) - Modal: name input → download → progress bar with % and MB counter - Raster styles show no download button (not supported by OfflineManager) Settings → App → Offline maps: - Lists all downloaded regions with size and tile count - Delete with confirm alert - Placeholder text when no regions exist --- src/screens/RecordingScreen.tsx | 114 +++++++++++++++++++++++++++++--- src/screens/SettingsScreen.tsx | 39 +++++++++++ src/services/offline.ts | 100 ++++++++++++++++++++++++++++ 3 files changed, 244 insertions(+), 9 deletions(-) create mode 100644 src/services/offline.ts diff --git a/src/screens/RecordingScreen.tsx b/src/screens/RecordingScreen.tsx index b7f2ad1..03861e1 100644 --- a/src/screens/RecordingScreen.tsx +++ b/src/screens/RecordingScreen.tsx @@ -1,23 +1,34 @@ import React, { useEffect, useRef, useState, useMemo } from 'react'; -import { View, Text, StyleSheet, TouchableOpacity, Alert } from 'react-native'; +import { View, Text, StyleSheet, TouchableOpacity, Alert, Modal, TextInput, Pressable } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { activateKeepAwakeAsync, deactivateKeepAwake } from 'expo-keep-awake'; -import { Map, Camera, GeoJSONSource, Layer, UserLocation } from '@maplibre/maplibre-react-native'; +import { Map, Camera, GeoJSONSource, Layer, UserLocation, type MapRef } from '@maplibre/maplibre-react-native'; import type { LineLayerStyle, CircleLayerStyle } from '@maplibre/maplibre-react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useRecordingStore } from '../store/recording'; import { startGpsRecording, stopGpsRecording, requestLocationPermissions, requestForegroundLocation } from '../services/gps'; import { RootStackParamList } from '../types'; import { colors } from '../theme'; import { useTheme } from '../ThemeContext'; import { MAP_STYLES } from '../mapStyles'; +import { downloadRegion, formatBytes, expandBounds, OFFLINE_STYLE_URL } from '../services/offline'; type Nav = NativeStackNavigationProp; export function RecordingScreen() { const nav = useNavigation