Skip to content

Commit b1ae0db

Browse files
committed
Android and IOS working maps
1 parent d871d74 commit b1ae0db

File tree

2 files changed

+73
-78
lines changed

2 files changed

+73
-78
lines changed

mobile/app/game/results.tsx

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'
22
import {
33
View,
44
Text,
5-
Image,
65
ScrollView,
76
StyleSheet,
87
Pressable,
@@ -15,8 +14,9 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
1514
import { useLocalSearchParams, useRouter } from 'expo-router';
1615
import { Ionicons } from '@expo/vector-icons';
1716
import { LinearGradient } from 'expo-linear-gradient';
18-
import MapView, { Marker, Polyline, Callout, PROVIDER_GOOGLE } from 'react-native-maps';
17+
import MapView, { Polyline, Callout, PROVIDER_GOOGLE } from 'react-native-maps';
1918
import { colors } from '../../src/shared';
19+
import PinMarker from '../../src/components/game/PinMarker';
2020

2121
const guessPinImage = require('../../assets/marker-src.png');
2222
const actualPinImage = require('../../assets/marker-dest.png');
@@ -113,6 +113,7 @@ export default function GameResultsScreen() {
113113

114114
const [activeRound, setActiveRound] = useState<number | null>(null);
115115
const [detailsExpanded, setDetailsExpanded] = useState(false);
116+
const [collapsedContentHeight, setCollapsedContentHeight] = useState(0);
116117

117118
// Animated panel height for smooth expand/collapse
118119
const panelAnim = useRef(new Animated.Value(0)).current; // 0 = collapsed, 1 = expanded
@@ -286,14 +287,12 @@ export default function GameResultsScreen() {
286287
return (
287288
<React.Fragment key={index}>
288289
{hasActual && (
289-
<Marker
290+
<PinMarker
290291
identifier={`actual-${index}`}
291292
coordinate={{ latitude: round.actualLat!, longitude: round.actualLong! }}
292-
anchor={{ x: 0.5, y: 1.0 }}
293-
tracksViewChanges={false}
293+
imageSource={actualPinImage}
294294
stopPropagation
295295
>
296-
<Image source={actualPinImage} style={styles.markerImage} resizeMode="contain" />
297296
<Callout onPress={() => openInGoogleMaps(round.actualLat!, round.actualLong!, round.panoId)}>
298297
<View style={styles.calloutContainer}>
299298
<Text style={styles.calloutTitle}>Round {index + 1} — Actual Location</Text>
@@ -310,17 +309,15 @@ export default function GameResultsScreen() {
310309
</View>
311310
</View>
312311
</Callout>
313-
</Marker>
312+
</PinMarker>
314313
)}
315314
{hasGuess && (
316-
<Marker
315+
<PinMarker
317316
identifier={`guess-${index}`}
318317
coordinate={{ latitude: round.guessLat, longitude: round.guessLong }}
319-
anchor={{ x: 0.5, y: 1.0 }}
320-
tracksViewChanges={false}
318+
imageSource={guessPinImage}
321319
stopPropagation
322320
>
323-
<Image source={guessPinImage} style={styles.markerImage} resizeMode="contain" />
324321
<Callout>
325322
<View style={styles.calloutContainer}>
326323
<Text style={styles.calloutTitle}>Round {index + 1} — Your Guess</Text>
@@ -334,7 +331,7 @@ export default function GameResultsScreen() {
334331
)}
335332
</View>
336333
</Callout>
337-
</Marker>
334+
</PinMarker>
338335
)}
339336
{hasActual && hasGuess && (
340337
<Polyline
@@ -580,7 +577,11 @@ export default function GameResultsScreen() {
580577
// ═══════════════════════════════════════════════════════════
581578
// PORTRAIT LAYOUT — map top, bottom sheet panel (like web mobile)
582579
// ═══════════════════════════════════════════════════════════
583-
const collapsedHeight = height * 0.35;
580+
const bottomInsetPadding = Math.max(insets.bottom, 8);
581+
const panelBottomPadding = detailsExpanded ? bottomInsetPadding : 26;
582+
const collapsedHeight = collapsedContentHeight > 0
583+
? collapsedContentHeight + panelBottomPadding
584+
: Math.min(height * 0.35, 240 + panelBottomPadding);
584585
const expandedHeight = height * 0.68;
585586

586587
const animatedPanelHeight = panelAnim.interpolate({
@@ -617,28 +618,42 @@ export default function GameResultsScreen() {
617618
styles.portraitPanel,
618619
{
619620
height: animatedPanelHeight,
620-
paddingBottom: Math.max(insets.bottom, 8),
621621
},
622622
]}
623623
>
624624
<LinearGradient
625625
colors={['rgba(20, 65, 25, 0.97)', 'rgba(20, 65, 25, 0.90)']}
626-
style={styles.portraitPanelGradient}
626+
style={[
627+
styles.portraitPanelGradient,
628+
{
629+
paddingBottom: panelBottomPadding,
630+
},
631+
]}
627632
>
628-
{/* Drag handle — tap to toggle details */}
629-
<Pressable onPress={toggleDetails} style={styles.handleBarTouchArea}>
630-
<View style={styles.handleBar} />
631-
</Pressable>
633+
<View
634+
onLayout={(event) => {
635+
if (detailsExpanded) return;
636+
const nextHeight = Math.ceil(event.nativeEvent.layout.height);
637+
if (Math.abs(nextHeight - collapsedContentHeight) >= 2) {
638+
setCollapsedContentHeight(nextHeight);
639+
}
640+
}}
641+
>
642+
{/* Drag handle — tap to toggle details */}
643+
<Pressable onPress={toggleDetails} style={styles.handleBarTouchArea}>
644+
<View style={styles.handleBar} />
645+
</Pressable>
632646

633-
{renderHeader(detailsExpanded)}
647+
{renderHeader(detailsExpanded)}
648+
</View>
634649

635650
{/* Rounds section — always rendered, animated via panel height */}
636651
{detailsExpanded && (
637652
<>
638653
<View style={styles.sidebarDivider} />
639654
<ScrollView
640655
style={{ flex: 1 }}
641-
contentContainerStyle={{ paddingBottom: spacing.lg }}
656+
contentContainerStyle={{ paddingBottom: spacing.lg + bottomInsetPadding }}
642657
showsVerticalScrollIndicator={true}
643658
bounces={true}
644659
nestedScrollEnabled={true}
@@ -663,6 +678,7 @@ const styles = StyleSheet.create({
663678
sidebar: {
664679
borderLeftWidth: 2,
665680
borderLeftColor: colors.primary,
681+
backgroundColor: 'rgba(20, 65, 25, 0.97)',
666682
},
667683
sidebarGradient: {
668684
flex: 1,
@@ -679,6 +695,7 @@ const styles = StyleSheet.create({
679695
left: 0,
680696
right: 0,
681697
overflow: 'hidden',
698+
backgroundColor: 'rgba(20, 65, 25, 0.97)',
682699
borderTopLeftRadius: 16,
683700
borderTopRightRadius: 16,
684701
...Platform.select({
@@ -856,13 +873,6 @@ const styles = StyleSheet.create({
856873
fontSize: fontSizes.xs,
857874
fontWeight: '500',
858875
},
859-
860-
// ── Map markers ──────────────────────────────────────────────
861-
markerImage: {
862-
width: 40,
863-
height: 50,
864-
},
865-
866876
// ── Callout tooltips ─────────────────────────────────────────
867877
calloutContainer: {
868878
minWidth: 180,

mobile/src/components/game/GuessMap.tsx

Lines changed: 35 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { useState, useRef, useEffect, useCallback } from 'react';
2-
import { View, StyleSheet, Platform, GestureResponderEvent, Image } from 'react-native';
3-
import MapView, { Marker, Polyline, PROVIDER_GOOGLE } from 'react-native-maps';
4-
import { Asset } from 'expo-asset';
1+
import { useRef, useEffect, useCallback } from 'react';
2+
import { View, StyleSheet, Platform, GestureResponderEvent } from 'react-native';
3+
import MapView, { Polyline, PROVIDER_GOOGLE } from 'react-native-maps';
54
import { colors } from '../../shared';
5+
import PinMarker from './PinMarker';
66

77
// Pre-create Asset objects (doesn't download yet)
88
const guessPinModule = require('../../../assets/marker-src.png');
@@ -18,9 +18,6 @@ interface GuessMapProps {
1818
const TAP_SLOP = 10; // max px movement to count as tap
1919
const TAP_MAX_MS = 300; // max duration to count as tap
2020
const MARKER_SCALE = 1.1;
21-
const MARKER_WIDTH = 40 * MARKER_SCALE;
22-
const MARKER_HEIGHT = 50 * MARKER_SCALE;
23-
const IOS_MARKER_CENTER_OFFSET = { x: 0, y: -(MARKER_HEIGHT / 2) };
2421

2522
export default function GuessMap({
2623
guessPosition,
@@ -32,15 +29,6 @@ export default function GuessMap({
3229
const touchStart = useRef({ x: 0, y: 0, time: 0 });
3330
const lastFastTap = useRef(0);
3431

35-
// Download marker assets to local filesystem so native Google Maps can load them
36-
const [pinUris, setPinUris] = useState<{ guess?: string; actual?: string }>({});
37-
useEffect(() => {
38-
(async () => {
39-
const [g, a] = await Asset.loadAsync([guessPinModule, actualPinModule]);
40-
setPinUris({ guess: g.localUri ?? g.uri, actual: a.localUri ?? a.uri });
41-
})();
42-
}, []);
43-
4432
// When showing result, fit both markers in view
4533
useEffect(() => {
4634
if (actualPosition && guessPosition && mapRef.current) {
@@ -50,7 +38,7 @@ export default function GuessMap({
5038
];
5139

5240
mapRef.current.fitToCoordinates(coordinates, {
53-
edgePadding: { top: 50, right: 50, bottom: 50, left: 50 },
41+
edgePadding: { top: 120, right: 120, bottom:300, left: 120 },
5442
animated: true,
5543
});
5644
}
@@ -124,6 +112,28 @@ export default function GuessMap({
124112
onMapPress(latitude, longitude);
125113
}, [actualPosition, onMapPress]);
126114

115+
const mapPins: Array<{
116+
id: 'guess' | 'actual';
117+
coordinate: { latitude: number; longitude: number };
118+
imageSource: number;
119+
}> = [];
120+
121+
if (guessPosition) {
122+
mapPins.push({
123+
id: 'guess',
124+
coordinate: { latitude: guessPosition.lat, longitude: guessPosition.lng },
125+
imageSource: guessPinModule,
126+
});
127+
}
128+
129+
if (actualPosition) {
130+
mapPins.push({
131+
id: 'actual',
132+
coordinate: { latitude: actualPosition.lat, longitude: actualPosition.lng },
133+
imageSource: actualPinModule,
134+
});
135+
}
136+
127137
return (
128138
<View
129139
style={styles.container}
@@ -150,35 +160,14 @@ export default function GuessMap({
150160
rotateEnabled={false}
151161
pitchEnabled={false}
152162
>
153-
{/* Guess marker */}
154-
{guessPosition && pinUris.guess && (
155-
<Marker
156-
coordinate={{
157-
latitude: guessPosition.lat,
158-
longitude: guessPosition.lng,
159-
}}
160-
anchor={{ x: 0.5, y: 1 }}
161-
centerOffset={Platform.OS === 'ios' ? IOS_MARKER_CENTER_OFFSET : undefined}
162-
tracksViewChanges={false}
163-
>
164-
<Image source={{ uri: pinUris.guess }} style={styles.markerImage} resizeMode="contain" />
165-
</Marker>
166-
)}
167-
168-
{/* Actual location marker */}
169-
{actualPosition && pinUris.actual && (
170-
<Marker
171-
coordinate={{
172-
latitude: actualPosition.lat,
173-
longitude: actualPosition.lng,
174-
}}
175-
anchor={{ x: 0.5, y: 1 }}
176-
centerOffset={Platform.OS === 'ios' ? IOS_MARKER_CENTER_OFFSET : undefined}
177-
tracksViewChanges={false}
178-
>
179-
<Image source={{ uri: pinUris.actual }} style={styles.markerImage} resizeMode="contain" />
180-
</Marker>
181-
)}
163+
{mapPins.map((pin) => (
164+
<PinMarker
165+
key={pin.id}
166+
coordinate={pin.coordinate}
167+
imageSource={pin.imageSource}
168+
scale={MARKER_SCALE}
169+
/>
170+
))}
182171

183172
{/* Line between guess and actual */}
184173
{guessPosition && actualPosition && (
@@ -204,8 +193,4 @@ const styles = StyleSheet.create({
204193
map: {
205194
flex: 1,
206195
},
207-
markerImage: {
208-
width: MARKER_WIDTH,
209-
height: MARKER_HEIGHT,
210-
},
211196
});

0 commit comments

Comments
 (0)