first version of the cruise lovers app, build 1.0.1, ready to be published on the app store
This commit is contained in:
76
assets/components/ContactMapPreview.tsx
Normal file
76
assets/components/ContactMapPreview.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { LoadingSpinner } from '@/assets/components/LoadingSpinner';
|
||||
import { colors } from '@/assets/styles/colors';
|
||||
import styles from '@/styles/screens/tabs/contactos.styles';
|
||||
import { FontAwesome } from '@expo/vector-icons';
|
||||
import Constants, { ExecutionEnvironment } from 'expo-constants';
|
||||
import { lazy, Suspense, useMemo, useState } from 'react';
|
||||
import { Image, Platform, Pressable, Text, View } from 'react-native';
|
||||
|
||||
const MAP_ZOOM = 15;
|
||||
const isExpoGo = Constants.executionEnvironment === ExecutionEnvironment.StoreClient;
|
||||
const useNativeMaps = !isExpoGo && (Platform.OS === 'ios' || Platform.OS === 'android');
|
||||
|
||||
const NativeMapPreview = useNativeMaps
|
||||
? lazy(() => import('./ContactMapPreviewNative'))
|
||||
: null;
|
||||
|
||||
const buildStaticMapUrl = (lat: number, lng: number): string => {
|
||||
const delta = 0.012;
|
||||
const bbox = [lng - delta, lat - delta, lng + delta, lat + delta].join(',');
|
||||
return `https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/export?bbox=${bbox}&bboxSR=4326&size=600,300&format=png&f=image`;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
lat: number;
|
||||
lng: number;
|
||||
title?: string;
|
||||
onPress: () => void;
|
||||
};
|
||||
|
||||
function StaticMapFallback({ lat, lng, onPress }: Props) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
const mapUrl = useMemo(() => buildStaticMapUrl(lat, lng), [lat, lng]);
|
||||
|
||||
return (
|
||||
<Pressable onPress={onPress} style={styles.mapImageWrap}>
|
||||
<Image
|
||||
source={{ uri: mapUrl }}
|
||||
style={styles.mapImage}
|
||||
resizeMode="cover"
|
||||
onLoadStart={() => {
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
}}
|
||||
onLoad={() => setLoading(false)}
|
||||
onError={() => {
|
||||
setLoading(false);
|
||||
setError(true);
|
||||
}}
|
||||
/>
|
||||
{loading && !error && (
|
||||
<View style={styles.mapOverlay}>
|
||||
<LoadingSpinner size="small" />
|
||||
</View>
|
||||
)}
|
||||
{error && (
|
||||
<View style={styles.mapOverlay}>
|
||||
<FontAwesome name="map-marker" size={28} color={colors.vermelho} />
|
||||
<Text style={styles.mapErrorText}>Mapa indisponível</Text>
|
||||
</View>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
export function ContactMapPreview(props: Props) {
|
||||
if (NativeMapPreview) {
|
||||
return (
|
||||
<Suspense fallback={<StaticMapFallback {...props} />}>
|
||||
<NativeMapPreview {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
return <StaticMapFallback {...props} />;
|
||||
}
|
||||
81
assets/components/ContactMapPreviewNative.tsx
Normal file
81
assets/components/ContactMapPreviewNative.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { colors } from '@/assets/styles/colors';
|
||||
import styles from '@/styles/screens/tabs/contactos.styles';
|
||||
import { AppleMaps, GoogleMaps } from 'expo-maps';
|
||||
import { useMemo } from 'react';
|
||||
import { Platform, Pressable, View } from 'react-native';
|
||||
|
||||
const MAP_ZOOM = 15;
|
||||
|
||||
const disabledGoogleUi = {
|
||||
scrollGesturesEnabled: false,
|
||||
zoomGesturesEnabled: false,
|
||||
rotationGesturesEnabled: false,
|
||||
tiltGesturesEnabled: false,
|
||||
zoomControlsEnabled: false,
|
||||
mapToolbarEnabled: false,
|
||||
compassEnabled: false,
|
||||
scaleBarEnabled: false,
|
||||
};
|
||||
|
||||
const disabledAppleUi = {
|
||||
compassEnabled: false,
|
||||
scaleBarEnabled: false,
|
||||
myLocationButtonEnabled: false,
|
||||
togglePitchEnabled: false,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
lat: number;
|
||||
lng: number;
|
||||
title?: string;
|
||||
onPress: () => void;
|
||||
};
|
||||
|
||||
export default function ContactMapPreviewNative({ lat, lng, title, onPress }: Props) {
|
||||
const cameraPosition = useMemo(
|
||||
() => ({
|
||||
coordinates: { latitude: lat, longitude: lng },
|
||||
zoom: MAP_ZOOM,
|
||||
}),
|
||||
[lat, lng]
|
||||
);
|
||||
|
||||
const marker = useMemo(
|
||||
() => ({
|
||||
id: 'agency',
|
||||
coordinates: { latitude: lat, longitude: lng },
|
||||
title,
|
||||
}),
|
||||
[lat, lng, title]
|
||||
);
|
||||
|
||||
return (
|
||||
<Pressable onPress={onPress} style={styles.mapImageWrap}>
|
||||
<View style={styles.mapImage} pointerEvents="none">
|
||||
{Platform.OS === 'ios' ? (
|
||||
<AppleMaps.View
|
||||
style={styles.mapImage}
|
||||
cameraPosition={cameraPosition}
|
||||
markers={[
|
||||
{
|
||||
...marker,
|
||||
systemImage: 'mappin',
|
||||
tintColor: colors.vermelho,
|
||||
},
|
||||
]}
|
||||
properties={{ selectionEnabled: false }}
|
||||
uiSettings={disabledAppleUi}
|
||||
/>
|
||||
) : (
|
||||
<GoogleMaps.View
|
||||
style={styles.mapImage}
|
||||
cameraPosition={cameraPosition}
|
||||
markers={[marker]}
|
||||
properties={{ selectionEnabled: false }}
|
||||
uiSettings={disabledGoogleUi}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useAuth } from '@/assets/contexts/useAuth';
|
||||
import { getCachedContacts } from '@/assets/services/offlineStorage';
|
||||
import PhoneIcon from '@/assets/icons/phone-solid.svg';
|
||||
import { colors } from '@/assets/styles/colors';
|
||||
import { fonts } from '@/assets/styles/fonts';
|
||||
import { FontAwesome } from '@expo/vector-icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Alert, Linking, Platform, StyleSheet, Text, useWindowDimensions } from 'react-native';
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||
@@ -27,7 +27,7 @@ export function EmergencyButton() {
|
||||
const tabBarHeight = (Platform.OS === 'ios' ? 54 : 64) + insets.bottom;
|
||||
const maxX = screenWidth - BTN_SIZE - MARGIN;
|
||||
const minY = insets.top + MARGIN;
|
||||
const maxY = screenHeight - tabBarHeight - BTN_SIZE - MARGIN;
|
||||
const maxY = screenHeight - tabBarHeight - BTN_SIZE + MARGIN;
|
||||
|
||||
const x = useSharedValue(screenWidth - BTN_SIZE - MARGIN);
|
||||
const y = useSharedValue(screenHeight - tabBarHeight - BTN_SIZE - MARGIN);
|
||||
@@ -49,7 +49,7 @@ export function EmergencyButton() {
|
||||
if (!emergencyPhone) return;
|
||||
const url = `tel:${emergencyPhone.replace(/\s/g, '')}`;
|
||||
Alert.alert(
|
||||
'Linha de Emergência 24h',
|
||||
'Ajuda ao viajante 24h',
|
||||
`Ligar para ${emergencyPhone}?`,
|
||||
[
|
||||
{ text: 'Cancelar', style: 'cancel' },
|
||||
@@ -116,8 +116,8 @@ export function EmergencyButton() {
|
||||
return (
|
||||
<GestureDetector gesture={composed}>
|
||||
<Animated.View style={[styles.btn, animatedStyle]}>
|
||||
<FontAwesome name="phone" size={18} color={colors.branco} />
|
||||
<Text style={styles.label}>SOS</Text>
|
||||
<PhoneIcon width={18} height={18} fill={colors.branco} />
|
||||
<Text style={styles.label}>Ajuda</Text>
|
||||
</Animated.View>
|
||||
</GestureDetector>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user