Bladeren bron

offline react-native-maps with nomadmania api

Viktoriia 1 jaar geleden
bovenliggende
commit
fc93850262
8 gewijzigde bestanden met toevoegingen van 461 en 12 verwijderingen
  1. 1 0
      %ProgramData%/Microsoft/Windows/UUS/State/_active.uusver
  2. 125 7
      App.js
  3. 54 0
      app/api/api.js
  4. 39 0
      app/database/db.js
  5. 47 0
      app/functions.js
  6. 7 0
      metro.config.js
  7. 180 4
      package-lock.json
  8. 8 1
      package.json

+ 1 - 0
%ProgramData%/Microsoft/Windows/UUS/State/_active.uusver

@@ -0,0 +1 @@
+1023.910.1172.0

+ 125 - 7
App.js

@@ -1,11 +1,129 @@
-import { StatusBar } from 'expo-status-bar';
-import { StyleSheet, Text, View } from 'react-native';
+import { StyleSheet, View, Platform } from 'react-native';
+import * as FileSystem from 'expo-file-system';
+import { useEffect, useState } from 'react';
+import MapView, { UrlTile, Geojson, Marker } from 'react-native-maps';
+
+import initDB from './app/database/db';
+import {fetchRegion, getForRegion} from './app/api/api';
+import {loadGeoJSONData, loadMarkersData} from './app/functions';
+
+import NetInfo from "@react-native-community/netinfo";
+
+const tilesBaseURL = 'https://maps.nomadmania.com/tiles_osm';
+const localTileDir = `${FileSystem.documentDirectory}tiles`;
 
 export default function App() {
+  const [isConnected, setIsConnected] = useState(null);
+  const [geoJSON, setGeoJSON] = useState(null);
+  const [markers, setMarkers] = useState(null);
+  const region = 226;
+
+  useEffect(() => {
+    const unsubscribe = NetInfo.addEventListener(state => {
+      setIsConnected(state.isConnected);
+    });
+  
+    return () => unsubscribe();
+  }, []);
+
+  useEffect(() => {
+    async function prepare() {
+      if (isConnected !== null) {
+        console.log("Connected: ", isConnected);
+        if (isConnected) {
+          await fetchRegion(region);
+          await getForRegion(region);
+        }
+        
+        await loadMarkersData().then(data => {
+          setMarkers(data);
+        }).catch(error => {
+          console.error("Error loading markers data: ", error);
+        })
+        
+        await loadGeoJSONData(region).then(data => {
+          setGeoJSON({
+            type: 'FeatureCollection',
+            features: [
+              {
+                type: 'Feature',
+                properties: {
+                },
+                geometry: {
+                  type: data?.type,
+                  coordinates: data?.coordinates,
+                }
+              }
+            ]
+          });
+        }).catch(error => {
+          console.error("Error loading GeoJSON data: ", error);
+        });
+      }
+    }
+
+    initDB();
+    prepare();
+  }, [isConnected]);
+
+  const renderLocalTiles = () => {
+    return (
+      <UrlTile
+        urlTemplate={`${tilesBaseURL}/{z}/{x}/{y}`}
+        maximumZ={8}
+        tileCachePath={`${localTileDir}/{z}/{x}/{y}`}
+        shouldReplaceMapContent={true}
+        minimumZ={0}
+        offlineMode={!isConnected}
+        opacity={1}
+        zIndex={1}
+      />
+    );
+  };
+
+  function renderGeoJSON() {
+    if (!geoJSON) return null;
+  
+    return (
+      <Geojson
+        geojson={geoJSON}
+        strokeColor="#3973ac"
+        fillColor="rgba(57, 115, 172, 0.2)"
+        strokeWidth={3}
+        zIndex={2}
+        fiilRule="evenodd"
+      />
+    );
+  };
+
+  const renderMarkers = () => {
+    if (!markers) return null;
+
+    return markers?.map((marker, idx) => (
+      <Marker
+        key={(idx)}
+        coordinate={{ latitude: parseFloat(marker.l), longitude: parseFloat(marker.g) }}
+        title={marker.n}
+        description={marker.d}
+        image={{uri: `https://nomadmania.com/static/img/series/${marker.i}`}}
+      />
+    ));
+  };
+
   return (
     <View style={styles.container}>
-      <Text>Open up App.js to start working on your app!</Text>
-      <StatusBar style="auto" />
+      <MapView
+        style={styles.map}
+        mapType={Platform.OS == 'android' ? 'none' : 'standard'}
+        zoomControlEnabled={true}
+        offlineMode={!isConnected}
+        maxZoomLevel={8}
+        minZoomLevel={0}
+      >
+        {renderLocalTiles()}
+        {renderGeoJSON()}
+        {renderMarkers()}
+      </MapView>
     </View>
   );
 }
@@ -13,8 +131,8 @@ export default function App() {
 const styles = StyleSheet.create({
   container: {
     flex: 1,
-    backgroundColor: '#fff',
-    alignItems: 'center',
-    justifyContent: 'center',
+  },
+  map: {
+    flex: 1,
   },
 });

+ 54 - 0
app/api/api.js

@@ -0,0 +1,54 @@
+import { db } from "../database/db";
+import axios from "axios";
+
+const headers = {
+  'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
+  'Nmtoken': '6B905B9C-32E1-4653-8038-FDB54A18DE3F'
+};
+
+export async function fetchRegion(region) {
+  const apiUrl = `https://nomadmania.com/static/geojson/${region}.geojson`
+
+  try {
+    const response = await axios.get(apiUrl, {region}, {headers});
+
+    if (response.data) {
+      await db.transaction(tx => {
+        tx.executeSql(
+          'INSERT INTO regions (region_id, data) VALUES (?, ?);',
+          [region, JSON.stringify(response.data)],
+          (res) => {},
+          (error) => { console.error("Error saving GeoJSON data: ", error); }
+        );
+      });
+    }
+
+  } catch (error) {
+    console.error('Error fetching data from API:', error);
+  }
+}
+
+export async function getForRegion(region) {
+  const apiUrl = 'https://nomadmania.com/webapi/series/get-for-region';
+
+  try {
+    const response = await axios.post(apiUrl, {region}, {headers});
+
+    if (response.data.data) {
+      const first100Markers = response.data.data.slice(0, 20);
+
+      first100Markers.forEach(async (marker) => {
+        await db.transaction(tx => {
+          tx.executeSql(
+            'INSERT INTO markers (a, d, g, i, marker_id, l, m, n, new, s, sid, v) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);',
+            [marker.a, marker.d, marker.g, marker.i, marker.marker_id, marker.l, marker.m, marker.n, marker.new, marker.s, marker.sid, marker.v],
+            (res) => {},
+            (error) => { console.error("Error saving GeoJSON data: ", error); }
+          );
+        });
+      });
+    }
+  } catch (error) {
+    console.error('Error fetching data from API:', error);
+  }
+}

+ 39 - 0
app/database/db.js

@@ -0,0 +1,39 @@
+import * as SQLite from 'expo-sqlite';
+
+function openDatabase() {
+  if (Platform.OS === "web") {
+    return {
+      transaction: () => {
+        return {
+          executeSql: () => {},
+        };
+      },
+    };
+  }
+
+  const db = SQLite.openDatabase("db.db");
+  return db;
+}
+
+export const db = openDatabase();
+
+export default function initDB() {
+  return new Promise((resolve, reject) => {
+    db.transaction((tx) => {
+      tx.executeSql(
+        'CREATE TABLE IF NOT EXISTS regions (id INTEGER PRIMARY KEY AUTOINCREMENT, region_id INTEGER, data TEXT);',
+        [],
+        resolve,
+        (_, error) => reject(error),
+      );
+      tx.executeSql(
+        'CREATE TABLE IF NOT EXISTS markers (id INTEGER PRIMARY KEY AUTOINCREMENT, a TEXT, d TEXT, g TEXT, i TEXT, marker_id INTEGER, l TEXT, m INTEGER, n TEXT, new INTEGER, s TEXT, sid INTEGER, v INTEGER);',
+        [],
+        resolve,
+        (_, error) => reject(error),
+      );
+    });
+  });
+}
+
+initDB();

+ 47 - 0
app/functions.js

@@ -0,0 +1,47 @@
+import { db } from "./database/db";
+
+export async function loadGeoJSONData(region) {
+  const selectQuery = `SELECT data FROM regions WHERE region_id = ${region};`;
+
+  return new Promise((resolve, reject) => {
+    db.transaction(tx => {
+      tx.executeSql(
+        selectQuery,
+        [],
+        (_, { rows }) => {
+          if (rows.length > 0) {
+            resolve(JSON.parse(rows._array[0].data));
+          } else {
+            resolve(null);
+          }
+        },
+        (_, error) => {
+          reject(error);
+        }
+      );
+    });
+  });
+}
+
+export async function loadMarkersData() {
+  const selectQuery = `SELECT marker_id, l, g, i, n, d FROM markers;`;
+
+  return new Promise((resolve, reject) => {
+    db.transaction(tx => {
+      tx.executeSql(
+        selectQuery,
+        [],
+        (_, { rows }) => {
+          if (rows.length > 0) {
+            resolve(rows._array.slice(0, 20));
+          } else {
+            resolve(null);
+          }
+        },
+        (_, error) => {
+          reject(error);
+        }
+      );
+    });
+  });
+}

+ 7 - 0
metro.config.js

@@ -0,0 +1,7 @@
+const { getDefaultConfig } = require('expo/metro-config');
+
+const defaultConfig = getDefaultConfig(__dirname);
+
+defaultConfig.resolver.assetExts.push('db');
+
+module.exports = defaultConfig;

+ 180 - 4
package-lock.json

@@ -8,10 +8,17 @@
       "name": "maps-offline-test",
       "version": "1.0.0",
       "dependencies": {
+        "@react-native-community/netinfo": "^11.1.0",
+        "axios": "^1.6.1",
         "expo": "~49.0.15",
+        "expo-asset": "^8.10.1",
+        "expo-file-system": "^15.6.0",
+        "expo-sqlite": "^11.6.0",
         "expo-status-bar": "~1.6.0",
         "react": "18.2.0",
-        "react-native": "0.72.6"
+        "react-native": "0.72.6",
+        "react-native-maps": "1.8.0",
+        "react-native-webview": "^13.6.2"
       },
       "devDependencies": {
         "@babel/core": "^7.20.0"
@@ -3103,6 +3110,18 @@
       "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-13.0.0.tgz",
       "integrity": "sha512-TI+l71+5aSKnShYclFa14Kum+hQMZ86b95SH6tQUG3qZEmLTarvWpKwqtTwQKqvlJSJrpFiSFu3eCuZokY6zWA=="
     },
+    "node_modules/@expo/websql": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@expo/websql/-/websql-1.0.1.tgz",
+      "integrity": "sha512-H9/t1V7XXyKC343FJz/LwaVBfDhs6IqhDtSYWpt8LNSQDVjf5NvVJLc5wp+KCpRidZx8+0+YeHJN45HOXmqjFA==",
+      "dependencies": {
+        "argsarray": "^0.0.1",
+        "immediate": "^3.2.2",
+        "noop-fn": "^1.0.0",
+        "pouchdb-collections": "^1.0.1",
+        "tiny-queue": "^0.2.1"
+      }
+    },
     "node_modules/@expo/xcpretty": {
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.2.2.tgz",
@@ -5756,6 +5775,14 @@
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
     },
+    "node_modules/@react-native-community/netinfo": {
+      "version": "11.1.0",
+      "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.1.0.tgz",
+      "integrity": "sha512-pIbCuqgrY7SkngAcjUs9fMzNh1h4soQMVw1IeGp1HN5//wox3fUVOuvyIubTscUbdLFKiltJAiuQek7Nhx1bqA==",
+      "peerDependencies": {
+        "react-native": ">=0.59"
+      }
+    },
     "node_modules/@react-native/assets-registry": {
       "version": "0.72.0",
       "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.72.0.tgz",
@@ -5855,6 +5882,11 @@
         "@sinonjs/commons": "^3.0.0"
       }
     },
+    "node_modules/@types/geojson": {
+      "version": "7946.0.13",
+      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz",
+      "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ=="
+    },
     "node_modules/@types/istanbul-lib-coverage": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -6079,6 +6111,11 @@
         "sprintf-js": "~1.0.2"
       }
     },
+    "node_modules/argsarray": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/argsarray/-/argsarray-0.0.1.tgz",
+      "integrity": "sha512-u96dg2GcAKtpTrBdDoFIM7PjcBA+6rSP0OR94MOReNRyUECL6MtQt5XXmRr4qrftYaef9+l5hcpO5te7sML1Cg=="
+    },
     "node_modules/array-union": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -6134,6 +6171,29 @@
         "node": ">= 4.0.0"
       }
     },
+    "node_modules/axios": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz",
+      "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==",
+      "dependencies": {
+        "follow-redirects": "^1.15.0",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/axios/node_modules/form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/babel-core": {
       "version": "7.0.0-bridge.0",
       "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz",
@@ -7463,6 +7523,17 @@
         "url-parse": "^1.5.9"
       }
     },
+    "node_modules/expo-asset/node_modules/expo-file-system": {
+      "version": "15.4.4",
+      "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-15.4.4.tgz",
+      "integrity": "sha512-F0xS88D85F7qVQ61r0qBnzh6VW/s6iIl+VaQEEi2nAIOQHw1JIEj4yCXPLTtbyn5VmArbe2dSL3KYz1V+BLkKA==",
+      "dependencies": {
+        "uuid": "^3.4.0"
+      },
+      "peerDependencies": {
+        "expo": "*"
+      }
+    },
     "node_modules/expo-constants": {
       "version": "14.4.2",
       "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-14.4.2.tgz",
@@ -7476,9 +7547,9 @@
       }
     },
     "node_modules/expo-file-system": {
-      "version": "15.4.4",
-      "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-15.4.4.tgz",
-      "integrity": "sha512-F0xS88D85F7qVQ61r0qBnzh6VW/s6iIl+VaQEEi2nAIOQHw1JIEj4yCXPLTtbyn5VmArbe2dSL3KYz1V+BLkKA==",
+      "version": "15.6.0",
+      "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-15.6.0.tgz",
+      "integrity": "sha512-a2hvSWPQLgzw6/u7QuVjVs44Zqgkq3EQJ94tUpw9GbAxj2RsdS3tPnzakBb3Mc6VoQ2Aop6FIgSKeYCeYJAzsg==",
       "dependencies": {
         "uuid": "^3.4.0"
       },
@@ -7627,11 +7698,33 @@
         "invariant": "^2.2.4"
       }
     },
+    "node_modules/expo-sqlite": {
+      "version": "11.6.0",
+      "resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-11.6.0.tgz",
+      "integrity": "sha512-R/CmBvY4JYit6e7nJiIplgvg9B5Us95ExmLNxiVsfYjwRqsvKvNlETxX6BpB4FahQleKAZVWOTAzs5o7kst5TA==",
+      "dependencies": {
+        "@expo/websql": "^1.0.1"
+      },
+      "peerDependencies": {
+        "expo": "*"
+      }
+    },
     "node_modules/expo-status-bar": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-1.6.0.tgz",
       "integrity": "sha512-e//Oi2WPdomMlMDD3skE4+1ZarKCJ/suvcB4Jo/nO427niKug5oppcPNYO+csR6y3ZglGuypS+3pp/hJ+Xp6fQ=="
     },
+    "node_modules/expo/node_modules/expo-file-system": {
+      "version": "15.4.4",
+      "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-15.4.4.tgz",
+      "integrity": "sha512-F0xS88D85F7qVQ61r0qBnzh6VW/s6iIl+VaQEEi2nAIOQHw1JIEj4yCXPLTtbyn5VmArbe2dSL3KYz1V+BLkKA==",
+      "dependencies": {
+        "uuid": "^3.4.0"
+      },
+      "peerDependencies": {
+        "expo": "*"
+      }
+    },
     "node_modules/fast-glob": {
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@@ -7829,6 +7922,25 @@
         "node": ">=0.4.0"
       }
     },
+    "node_modules/follow-redirects": {
+      "version": "1.15.3",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
+      "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/fontfaceobserver": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz",
@@ -8263,6 +8375,11 @@
         "node": ">=14.0.0"
       }
     },
+    "node_modules/immediate": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz",
+      "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q=="
+    },
     "node_modules/import-fresh": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
@@ -10926,6 +11043,11 @@
         "url": "https://github.com/sponsors/antelle"
       }
     },
+    "node_modules/noop-fn": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/noop-fn/-/noop-fn-1.0.0.tgz",
+      "integrity": "sha512-pQ8vODlgXt2e7A3mIbFDlizkr46r75V+BJxVAyat8Jl7YmI513gG5cfyRL0FedKraoZ+VAouI1h4/IWpus5pcQ=="
+    },
     "node_modules/normalize-path": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -11512,6 +11634,11 @@
         "node": "^10 || ^12 || >=14"
       }
     },
+    "node_modules/pouchdb-collections": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/pouchdb-collections/-/pouchdb-collections-1.0.1.tgz",
+      "integrity": "sha512-31db6JRg4+4D5Yzc2nqsRqsA2oOkZS8DpFav3jf/qVNBxusKa2ClkEIZ2bJNpaDbMfWtnuSq59p6Bn+CipPMdg=="
+    },
     "node_modules/pretty-bytes": {
       "version": "5.6.0",
       "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@@ -11620,6 +11747,11 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
     },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
     "node_modules/pump": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -11816,6 +11948,45 @@
         "react": "18.2.0"
       }
     },
+    "node_modules/react-native-maps": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-1.8.0.tgz",
+      "integrity": "sha512-KfVZ03L42+a22Fkcl2uDe5d+73BweTlcRUzm2G+aQUJ7fHrBxj7CuXWtKMSNpVDiO3YDCWcshiiyjSXxkd/qOw==",
+      "dependencies": {
+        "@types/geojson": "^7946.0.10"
+      },
+      "peerDependencies": {
+        "react": ">= 17.0.1",
+        "react-native": ">= 0.64.3",
+        "react-native-web": ">= 0.11"
+      },
+      "peerDependenciesMeta": {
+        "react-native-web": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/react-native-webview": {
+      "version": "13.6.2",
+      "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.6.2.tgz",
+      "integrity": "sha512-QzhQ5JCU+Nf2W285DtvCZOVQy/MkJXMwNDYPZvOWQbAOgxJMSSO+BtqXTMA1UPugDsko6PxJ0TxSlUwIwJijDg==",
+      "dependencies": {
+        "escape-string-regexp": "2.0.0",
+        "invariant": "2.2.4"
+      },
+      "peerDependencies": {
+        "react": "*",
+        "react-native": "*"
+      }
+    },
+    "node_modules/react-native-webview/node_modules/escape-string-regexp": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+      "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/react-native/node_modules/promise": {
       "version": "8.3.0",
       "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz",
@@ -12862,6 +13033,11 @@
         "xtend": "~4.0.1"
       }
     },
+    "node_modules/tiny-queue": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/tiny-queue/-/tiny-queue-0.2.1.tgz",
+      "integrity": "sha512-EijGsv7kzd9I9g0ByCl6h42BWNGUZrlCSejfrb3AKeHC33SGbASu1VDf5O3rRiiUOhAC9CHdZxFPbZu0HmR70A=="
+    },
     "node_modules/tmp": {
       "version": "0.0.33",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",

+ 8 - 1
package.json

@@ -9,10 +9,17 @@
     "web": "expo start --web"
   },
   "dependencies": {
+    "@react-native-community/netinfo": "^11.1.0",
+    "axios": "^1.6.1",
     "expo": "~49.0.15",
+    "expo-asset": "^8.10.1",
+    "expo-file-system": "^15.6.0",
+    "expo-sqlite": "^11.6.0",
     "expo-status-bar": "~1.6.0",
     "react": "18.2.0",
-    "react-native": "0.72.6"
+    "react-native": "0.72.6",
+    "react-native-maps": "1.8.0",
+    "react-native-webview": "^13.6.2"
   },
   "devDependencies": {
     "@babel/core": "^7.20.0"