|
@@ -1,467 +0,0 @@
|
|
|
-import {
|
|
|
- StyleSheet,
|
|
|
- View,
|
|
|
- Platform,
|
|
|
- TouchableOpacity,
|
|
|
- Text
|
|
|
-} from 'react-native';
|
|
|
-import React, { useEffect, useState, useRef, useMemo } from 'react';
|
|
|
-import MapView, { UrlTile, Geojson } from 'react-native-maps';
|
|
|
-import * as turf from '@turf/turf';
|
|
|
-import * as FileSystem from 'expo-file-system';
|
|
|
-import * as Location from 'expo-location';
|
|
|
-
|
|
|
-import MenuIcon from '../../../assets/icons/menu.svg';
|
|
|
-import SearchIcon from '../../../assets/icons/search.svg';
|
|
|
-import RadarIcon from '../../../assets/icons/radar.svg';
|
|
|
-import LocationIcon from '../../../assets/icons/location.svg';
|
|
|
-import CloseSvg from '../../../assets/icons/close.svg';
|
|
|
-
|
|
|
-import regions from '../../../assets/geojson/nm2022.json'
|
|
|
-import dareRegions from '../../../assets/geojson/mqp.json'
|
|
|
-
|
|
|
-import NetInfo from "@react-native-community/netinfo";
|
|
|
-import { getFirstDatabase, getSecondDatabase } from '../../db';
|
|
|
-import RegionPopup from '../../components/RegionPopup';
|
|
|
-
|
|
|
-import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
|
|
|
-import { SQLiteDatabase } from 'expo-sqlite';
|
|
|
-import { FeatureCollection } from '@turf/turf';
|
|
|
-
|
|
|
-const tilesBaseURL = 'https://maps.nomadmania.com/tiles_osm';
|
|
|
-const localTileDir = `${FileSystem.cacheDirectory}tiles`;
|
|
|
-
|
|
|
-const gridUrl = 'https://maps.nomadmania.com/tiles_nm/grid';
|
|
|
-const localGridDir = `${FileSystem.cacheDirectory}tiles/grid`;
|
|
|
-
|
|
|
-const visitedTiles = 'https://maps.nomadmania.com/tiles_nm/user_visited/51363';
|
|
|
-const localVisitedDir = `${FileSystem.cacheDirectory}tiles/user_visited`;
|
|
|
-
|
|
|
-const dareTiles = 'https://maps.nomadmania.com/tiles_nm/regions_mqp';
|
|
|
-const localDareDir = `${FileSystem.cacheDirectory}tiles/regions_mqp`;
|
|
|
-
|
|
|
-interface RegionData {
|
|
|
- type?: string;
|
|
|
- name?: string;
|
|
|
- crs?: {
|
|
|
- type: string;
|
|
|
- properties: { name: string; };
|
|
|
- };
|
|
|
- features?: any;
|
|
|
-}
|
|
|
-
|
|
|
-interface Region {
|
|
|
- id: number;
|
|
|
- name: string;
|
|
|
- region_photos: string;
|
|
|
- visitors_count: number;
|
|
|
-}
|
|
|
-
|
|
|
-interface Feature {
|
|
|
- geometry: turf.helpers.Geometry;
|
|
|
- properties: {
|
|
|
- id: number;
|
|
|
- fill?: string;
|
|
|
- stroke?: string;
|
|
|
- };
|
|
|
- type: 'Feature';
|
|
|
-}
|
|
|
-
|
|
|
-interface GeojsonRegion {
|
|
|
- type: 'FeatureCollection';
|
|
|
- features: Feature[];
|
|
|
-}
|
|
|
-
|
|
|
-type HomeScreenNavigationProp = BottomTabNavigationProp<any>;
|
|
|
-interface HomeScreenProps {
|
|
|
- navigation: HomeScreenNavigationProp;
|
|
|
-}
|
|
|
-
|
|
|
-const HomeScreen: React.FC<HomeScreenProps> = ({ navigation }) => {
|
|
|
- const mapRef = useRef<MapView>(null);
|
|
|
-
|
|
|
- const [isConnected, setIsConnected] = useState<boolean | null>(true);
|
|
|
- const [selectedRegion, setSelectedRegion] = useState(null);
|
|
|
- const [location, setLocation] = useState(null);
|
|
|
- const [popupVisible, setPopupVisible] = useState<boolean | null>(false);
|
|
|
- const [regionData, setRegionData] = useState<Region | null>(null);
|
|
|
- const [userAvatars, setUserAvatars] = useState<string[]>([]);
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- navigation.setOptions({
|
|
|
- tabBarStyle: {
|
|
|
- display: popupVisible ? 'none' : 'flex',
|
|
|
- position: 'absolute',
|
|
|
- ...Platform.select({
|
|
|
- android: {
|
|
|
- height: 58,
|
|
|
- },
|
|
|
- }),
|
|
|
- }
|
|
|
- });
|
|
|
- }, [popupVisible, navigation]);
|
|
|
-
|
|
|
- // useEffect(() => {
|
|
|
- // (async () => {
|
|
|
-
|
|
|
- // let { status } = await Location.requestForegroundPermissionsAsync();
|
|
|
- // if (status !== 'granted') {
|
|
|
- // return;
|
|
|
- // }
|
|
|
-
|
|
|
- // let location = await Location.getCurrentPositionAsync({});
|
|
|
- // setLocation({
|
|
|
- // latitude: location.coords.latitude,
|
|
|
- // longitude: location.coords.longitude,
|
|
|
- // latitudeDelta: 5,
|
|
|
- // longitudeDelta: 5,
|
|
|
- // });
|
|
|
- // })();
|
|
|
- // }, []);
|
|
|
-
|
|
|
- const getData = async (db: SQLiteDatabase | null, regionId: number, name: string) => {
|
|
|
- return new Promise<void>((resolve, reject) => {
|
|
|
- db?.transaction(tx => {
|
|
|
- tx.executeSql(
|
|
|
- `SELECT * FROM ${name} WHERE id = ${regionId};`,
|
|
|
- [],
|
|
|
- (_, { rows }) => {
|
|
|
- setRegionData(rows._array[0]);
|
|
|
-
|
|
|
- const avatarPromises = JSON.parse(rows._array[0].visitors_avatars)?.map((avatarId: number) => {
|
|
|
- return new Promise((resolveAvatar, rejectAvatar) => {
|
|
|
- tx.executeSql(
|
|
|
- `SELECT * FROM avatars WHERE id = ${avatarId};`,
|
|
|
- [],
|
|
|
- (_, { rows }) => {
|
|
|
- resolveAvatar(rows._array[0].data);
|
|
|
- },
|
|
|
- (_, error) => {
|
|
|
- console.error('error', error);
|
|
|
- reject(error);
|
|
|
- return false;
|
|
|
- }
|
|
|
- );
|
|
|
- });
|
|
|
- });
|
|
|
-
|
|
|
- Promise.all(avatarPromises)
|
|
|
- .then(avatars => {
|
|
|
- setUserAvatars(avatars);
|
|
|
- resolve();
|
|
|
- })
|
|
|
- .catch(error => {
|
|
|
- console.error('Error processing avatars', error);
|
|
|
- reject(error);
|
|
|
- });
|
|
|
- },
|
|
|
- (_, error) => {
|
|
|
- console.error('error', error);
|
|
|
- reject(error);
|
|
|
- return false;
|
|
|
- }
|
|
|
- );
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- const handleMapPress = async (event: { nativeEvent: { coordinate: { latitude: any; longitude: any; }; }; }) => {
|
|
|
- const { latitude, longitude } = event.nativeEvent.coordinate;
|
|
|
- const point = turf.point([longitude, latitude]);
|
|
|
- setUserAvatars([]);
|
|
|
-
|
|
|
- const findRegion = (dataset: RegionData) => {
|
|
|
- return dataset.features.find((region: any) => {
|
|
|
- const coordinates = region?.geometry?.coordinates;
|
|
|
- const type = region?.geometry?.type;
|
|
|
-
|
|
|
- if (!Array.isArray(coordinates) || coordinates.length === 0) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- const polygon: any = type === 'Polygon'
|
|
|
- ? turf.polygon(coordinates)
|
|
|
- : turf.multiPolygon(coordinates);
|
|
|
-
|
|
|
- return turf.booleanPointInPolygon(point, polygon);
|
|
|
- } catch (error) {
|
|
|
- console.error('Error creating polygon:', error);
|
|
|
- return false;
|
|
|
- }
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
- let db = getSecondDatabase();
|
|
|
- let tableName = 'places';
|
|
|
-
|
|
|
- let foundRegion = findRegion(dareRegions);
|
|
|
-
|
|
|
- if (!foundRegion) {
|
|
|
- foundRegion = findRegion(regions);
|
|
|
- db = getFirstDatabase();
|
|
|
- tableName = 'regions';
|
|
|
- }
|
|
|
- if (foundRegion) {
|
|
|
- const id = foundRegion.properties.id;
|
|
|
-
|
|
|
-
|
|
|
- // console.log('foundRegion', foundRegion)
|
|
|
-
|
|
|
- setSelectedRegion({
|
|
|
- type: 'FeatureCollection',
|
|
|
- features: [{
|
|
|
- geometry: foundRegion.geometry,
|
|
|
- properties: {
|
|
|
- ...foundRegion.properties,
|
|
|
- fill: "rgba(57, 115, 172, 0.2)",
|
|
|
- stroke: "#3973ac",
|
|
|
- },
|
|
|
- type: 'Feature',
|
|
|
- }]
|
|
|
- });
|
|
|
-
|
|
|
- await getData(db, id, tableName).then(() => {
|
|
|
- setPopupVisible(true)
|
|
|
- });
|
|
|
-
|
|
|
- const bounds = turf.bbox(foundRegion);
|
|
|
- const padding = 1;
|
|
|
-
|
|
|
- const region = {
|
|
|
- latitude: (bounds[1] + bounds[3]) / 2,
|
|
|
- longitude: (bounds[0] + bounds[2]) / 2,
|
|
|
- latitudeDelta: Math.abs(bounds[3] - bounds[1]) + padding,
|
|
|
- longitudeDelta: Math.abs(bounds[2] - bounds[0]) + padding,
|
|
|
- };
|
|
|
-
|
|
|
- mapRef.current?.animateToRegion(region, 1000);
|
|
|
- } else {
|
|
|
- handleClosePopup();
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- const unsubscribe = NetInfo.addEventListener(state => {
|
|
|
- setIsConnected(state.isConnected);
|
|
|
- });
|
|
|
-
|
|
|
- return () => unsubscribe();
|
|
|
- }, []);
|
|
|
-
|
|
|
- const renderLocalTiles = () => {
|
|
|
- return (
|
|
|
- <UrlTile
|
|
|
- urlTemplate={`${tilesBaseURL}/{z}/{x}/{y}`}
|
|
|
- maximumZ={15}
|
|
|
- maximumNativeZ={13}
|
|
|
- tileCachePath={`${localTileDir}`}
|
|
|
- shouldReplaceMapContent
|
|
|
- minimumZ={0}
|
|
|
- offlineMode={!isConnected}
|
|
|
- opacity={1}
|
|
|
- zIndex={1}
|
|
|
- />
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- const renderGridTiles = () => {
|
|
|
- return (
|
|
|
- <UrlTile
|
|
|
- urlTemplate={`${gridUrl}/{z}/{x}/{y}`}
|
|
|
- maximumZ={15}
|
|
|
- maximumNativeZ={13}
|
|
|
- tileCachePath={`${localGridDir}`}
|
|
|
- shouldReplaceMapContent
|
|
|
- minimumZ={0}
|
|
|
- offlineMode={!isConnected}
|
|
|
- opacity={1}
|
|
|
- zIndex={2}
|
|
|
- />
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- const renderVisitedTiles = () => {
|
|
|
- return (
|
|
|
- <UrlTile
|
|
|
- urlTemplate={`${visitedTiles}/{z}/{x}/{y}`}
|
|
|
- maximumZ={15}
|
|
|
- maximumNativeZ={13}
|
|
|
- tileCachePath={`${localVisitedDir}`}
|
|
|
- shouldReplaceMapContent
|
|
|
- minimumZ={0}
|
|
|
- offlineMode={!isConnected}
|
|
|
- opacity={0.5}
|
|
|
- zIndex={2}
|
|
|
- />
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- const renderDareTiles = () => {
|
|
|
- return (
|
|
|
- <UrlTile
|
|
|
- urlTemplate={`${dareTiles}/{z}/{x}/{y}`}
|
|
|
- maximumZ={15}
|
|
|
- maximumNativeZ={13}
|
|
|
- tileCachePath={`${localDareDir}`}
|
|
|
- shouldReplaceMapContent
|
|
|
- minimumZ={0}
|
|
|
- offlineMode={!isConnected}
|
|
|
- opacity={0.5}
|
|
|
- zIndex={2}
|
|
|
- />
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- function renderGeoJSON() {
|
|
|
- if (!selectedRegion) return null;
|
|
|
-
|
|
|
- return (
|
|
|
- <Geojson
|
|
|
- geojson={selectedRegion}
|
|
|
- fillColor="rgba(57, 115, 172, 0.2)"
|
|
|
- strokeColor="#3973ac"
|
|
|
- strokeWidth={Platform.OS == 'android' ? 3 : 2}
|
|
|
- zIndex={3}
|
|
|
- />
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- const handleClosePopup = () => {
|
|
|
- setPopupVisible(false);
|
|
|
- setSelectedRegion(null);
|
|
|
- }
|
|
|
-
|
|
|
- const renderedGeoJSON = useMemo(() => renderGeoJSON(), [selectedRegion]);
|
|
|
-
|
|
|
- return (
|
|
|
- <View style={styles.container}>
|
|
|
- <MapView
|
|
|
- ref={mapRef}
|
|
|
- // initialRegion={location}
|
|
|
- // showsUserLocation={true}
|
|
|
- showsMyLocationButton={false}
|
|
|
- showsCompass={false}
|
|
|
- zoomControlEnabled={false}
|
|
|
- onPress={handleMapPress}
|
|
|
- style={styles.map}
|
|
|
- mapType={Platform.OS == 'android' ? 'none' : 'standard'}
|
|
|
- offlineMode={!isConnected}
|
|
|
- maxZoomLevel={15}
|
|
|
- minZoomLevel={0}
|
|
|
- >
|
|
|
- {renderLocalTiles()}
|
|
|
- {renderedGeoJSON}
|
|
|
- {renderGridTiles()}
|
|
|
- {renderVisitedTiles()}
|
|
|
- {renderDareTiles()}
|
|
|
- </MapView>
|
|
|
-
|
|
|
- {!popupVisible ? (
|
|
|
- <>
|
|
|
- <TouchableOpacity style={[styles.cornerButton, styles.topLeftButton]}>
|
|
|
- <MenuIcon />
|
|
|
- </TouchableOpacity>
|
|
|
-
|
|
|
- <TouchableOpacity style={[styles.cornerButton, styles.topRightButton]}>
|
|
|
- <SearchIcon />
|
|
|
- </TouchableOpacity>
|
|
|
-
|
|
|
- <TouchableOpacity style={[styles.cornerButton, styles.bottomLeftButton]}>
|
|
|
- <RadarIcon />
|
|
|
- </TouchableOpacity>
|
|
|
-
|
|
|
- <TouchableOpacity style={[styles.cornerButton, styles.bottomRightButton]}>
|
|
|
- <LocationIcon />
|
|
|
- </TouchableOpacity>
|
|
|
- </>
|
|
|
- ) : (
|
|
|
- <>
|
|
|
- <TouchableOpacity style={[styles.cornerButton, styles.topLeftButton, styles.closeLeftButton]} onPress={handleClosePopup}>
|
|
|
- <CloseSvg fill="white" width={13} height={13} />
|
|
|
- <Text style={{fontSize: 12, color: 'white', fontWeight: '500', lineHeight: 14}}>Close</Text>
|
|
|
- </TouchableOpacity>
|
|
|
-
|
|
|
- <TouchableOpacity style={[styles.cornerButton, styles.topRightButton, {width: 42, height: 42, borderRadius: 21,}]}>
|
|
|
- <LocationIcon />
|
|
|
- </TouchableOpacity>
|
|
|
-
|
|
|
- <RegionPopup
|
|
|
- region={regionData}
|
|
|
- userAvatars={userAvatars}
|
|
|
- onMarkVisited={() => console.log('Mark as visited')}
|
|
|
- />
|
|
|
- </>
|
|
|
- )}
|
|
|
- </View>
|
|
|
- );
|
|
|
-}
|
|
|
-
|
|
|
-export default HomeScreen;
|
|
|
-
|
|
|
-const styles = StyleSheet.create({
|
|
|
- container: {
|
|
|
- ...StyleSheet.absoluteFillObject,
|
|
|
- alignItems: 'center',
|
|
|
- justifyContent: 'flex-end',
|
|
|
- },
|
|
|
- map: {
|
|
|
- ...StyleSheet.absoluteFillObject,
|
|
|
- },
|
|
|
- btn: {
|
|
|
- marginBottom: 5,
|
|
|
- },
|
|
|
- button: {
|
|
|
- backgroundColor: '#007bff',
|
|
|
- padding: 10,
|
|
|
- borderRadius: 5,
|
|
|
- alignItems: 'center',
|
|
|
- justifyContent: 'center',
|
|
|
- },
|
|
|
- cornerButton: {
|
|
|
- position: 'absolute',
|
|
|
- backgroundColor: 'rgba(255, 255, 255, 1)',
|
|
|
- padding: 12,
|
|
|
- width: 48,
|
|
|
- height: 48,
|
|
|
- borderRadius: 24,
|
|
|
- alignItems: 'center',
|
|
|
- justifyContent: 'center',
|
|
|
- shadowColor: 'rgba(33, 37, 41, 0.12)',
|
|
|
- shadowOffset: { width: 0, height: 4 },
|
|
|
- shadowRadius: 8,
|
|
|
- elevation: 5,
|
|
|
- },
|
|
|
- topLeftButton: {
|
|
|
- top: 44,
|
|
|
- left: 16,
|
|
|
- },
|
|
|
- closeLeftButton: {
|
|
|
- backgroundColor: 'rgba(33, 37, 41, 0.78)',
|
|
|
- paddingHorizontal: 12,
|
|
|
- paddingVertical: 8,
|
|
|
- width: 81,
|
|
|
- height: 36,
|
|
|
- borderRadius: 18,
|
|
|
- flexDirection: 'row',
|
|
|
- gap: 6,
|
|
|
- },
|
|
|
- topRightButton: {
|
|
|
- top: 44,
|
|
|
- right: 16,
|
|
|
- },
|
|
|
- bottomLeftButton: {
|
|
|
- bottom: Platform.OS == 'android' ? 80 : 100,
|
|
|
- left: 16,
|
|
|
- width: 42,
|
|
|
- height: 42,
|
|
|
- borderRadius: 21,
|
|
|
- },
|
|
|
- bottomRightButton: {
|
|
|
- bottom: Platform.OS == 'android' ? 80 : 100,
|
|
|
- right: 16,
|
|
|
- width: 42,
|
|
|
- height: 42,
|
|
|
- borderRadius: 21,
|
|
|
- },
|
|
|
-});
|