feat: tap map thumbnail to open full-screen interactive map
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Camera, GeoJSONSource, Layer, Map } from '@maplibre/maplibre-react-native';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
import { ActivityIndicator, 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';
|
||||
|
||||
@@ -121,6 +121,8 @@ export default function ActivityScreen() {
|
||||
// ── Map ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
function RouteMap({ geojson, loading }: { geojson: object | null; loading: boolean }) {
|
||||
const [fullscreen, setFullscreen] = useState(false);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View style={styles.mapPlaceholder}>
|
||||
@@ -131,25 +133,7 @@ function RouteMap({ geojson, loading }: { geojson: object | null; loading: boole
|
||||
if (!geojson) return null;
|
||||
|
||||
const bounds = geoJsonBounds(geojson);
|
||||
|
||||
return (
|
||||
<View style={styles.mapContainer}>
|
||||
<Map
|
||||
style={styles.map}
|
||||
mapStyle={MAP_STYLE}
|
||||
dragPan={false}
|
||||
touchZoom={false}
|
||||
touchPitch={false}
|
||||
touchRotate={false}
|
||||
>
|
||||
{bounds && (
|
||||
<Camera
|
||||
initialViewState={{
|
||||
bounds,
|
||||
padding: { top: 24, bottom: 24, left: 24, right: 24 },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
const routeSource = (
|
||||
<GeoJSONSource id="route" data={geojson as GeoJSON.FeatureCollection}>
|
||||
<Layer
|
||||
type="line"
|
||||
@@ -158,8 +142,49 @@ function RouteMap({ geojson, loading }: { geojson: object | null; loading: boole
|
||||
layout={{ 'line-cap': 'round', 'line-join': 'round' }}
|
||||
/>
|
||||
</GeoJSONSource>
|
||||
);
|
||||
const camera = bounds ? (
|
||||
<Camera
|
||||
initialViewState={{
|
||||
bounds,
|
||||
padding: { top: 24, bottom: 24, left: 24, right: 24 },
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Thumbnail — tap to expand */}
|
||||
<Pressable style={styles.mapContainer} onPress={() => setFullscreen(true)}>
|
||||
<Map
|
||||
style={styles.map}
|
||||
mapStyle={MAP_STYLE}
|
||||
dragPan={false}
|
||||
touchZoom={false}
|
||||
touchPitch={false}
|
||||
touchRotate={false}
|
||||
>
|
||||
{camera}
|
||||
{routeSource}
|
||||
</Map>
|
||||
<View style={styles.mapExpandHint}>
|
||||
<Text style={styles.mapExpandText}>⤢ tap to explore</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
|
||||
{/* Full-screen interactive map */}
|
||||
<Modal visible={fullscreen} animationType="slide" onRequestClose={() => setFullscreen(false)}>
|
||||
<View style={styles.fullscreenMap}>
|
||||
<Map style={styles.map} mapStyle={MAP_STYLE}>
|
||||
{camera}
|
||||
{routeSource}
|
||||
</Map>
|
||||
<Pressable style={styles.closeButton} onPress={() => setFullscreen(false)}>
|
||||
<Text style={styles.closeText}>✕</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -281,6 +306,11 @@ const styles = StyleSheet.create({
|
||||
mapContainer: { height: 220, marginBottom: 16, borderTopWidth: 1, borderBottomWidth: 1, borderColor: '#27272a' },
|
||||
map: { flex: 1 },
|
||||
mapPlaceholder: { height: 220, backgroundColor: '#18181b', alignItems: 'center', justifyContent: 'center', borderTopWidth: 1, borderBottomWidth: 1, borderColor: '#27272a', marginBottom: 16 },
|
||||
mapExpandHint: { position: 'absolute', bottom: 8, right: 8, backgroundColor: 'rgba(0,0,0,0.55)', borderRadius: 6, paddingHorizontal: 8, paddingVertical: 4 },
|
||||
mapExpandText: { color: '#a1a1aa', fontSize: 11 },
|
||||
fullscreenMap: { flex: 1, backgroundColor: '#09090b' },
|
||||
closeButton: { position: 'absolute', top: 56, right: 16, backgroundColor: 'rgba(0,0,0,0.6)', borderRadius: 20, width: 36, height: 36, alignItems: 'center', justifyContent: 'center' },
|
||||
closeText: { color: '#fff', fontSize: 16 },
|
||||
chartContainer: { marginHorizontal: 16, marginBottom: 16, backgroundColor: '#18181b', borderRadius: 10, borderWidth: 1, borderColor: '#27272a', padding: 12, alignItems: 'flex-start' },
|
||||
chartPlaceholder: { height: 120, backgroundColor: '#18181b', alignItems: 'center', justifyContent: 'center', borderRadius: 10, borderWidth: 1, borderColor: '#27272a', marginHorizontal: 16, marginBottom: 16 },
|
||||
chartLabel: { color: '#3f3f46', fontSize: 10, marginBottom: 2 },
|
||||
|
||||
Reference in New Issue
Block a user