T@7y{M*NWPCZpjo^cPRK=isEV&
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
literal 0
HcmV?d00001
diff --git a/components/UserHeader.js b/components/UserHeader.js
new file mode 100644
index 0000000..d18f1d2
--- /dev/null
+++ b/components/UserHeader.js
@@ -0,0 +1,82 @@
+import React, { useEffect, useState } from 'react';
+import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
+import { supabase } from '../lib/supabase';
+import { Ionicons } from '@expo/vector-icons';
+
+const fallbackAvatar = require('../assets/avatar.jpg');
+
+const UserHeader = () => {
+ const [user, setUser] = useState(null);
+
+ useEffect(() => {
+ const fetchUser = async () => {
+ const { data } = await supabase.auth.getUser();
+ if (data?.user) {
+ const { user } = data;
+ const displayName =
+ user.user_metadata?.full_name ||
+ user.user_metadata?.name ||
+ user.email;
+ const avatar =
+ user.user_metadata?.avatar_url ||
+ user.user_metadata?.picture ||
+ null;
+ setUser({
+ name: displayName,
+ avatar,
+ });
+ }
+ };
+ fetchUser();
+ }, []);
+
+ return (
+
+
+
+ Hey, {user?.name || 'User'}
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: '#fff',
+ margin: 10,
+ marginTop: 40,
+ padding: 16,
+ borderRadius: 16,
+ shadowColor: '#000',
+ shadowOpacity: 0.05,
+ shadowRadius: 8,
+ elevation: 2,
+ justifyContent: 'space-between',
+ },
+ avatar: {
+ width: 40,
+ height: 40,
+ borderRadius: 12,
+ marginRight: 16,
+ },
+ greeting: {
+ flex: 1,
+ fontSize: 18,
+ fontWeight: '500',
+ color: '#333',
+ textAlign: 'center',
+ },
+ name: {
+ fontWeight: 'bold',
+ },
+});
+
+export default UserHeader;
\ No newline at end of file
diff --git a/screens/HomeScreen.js b/screens/HomeScreen.js
index 0831cb0..81648e9 100644
--- a/screens/HomeScreen.js
+++ b/screens/HomeScreen.js
@@ -4,6 +4,7 @@ import { useNavigation } from '@react-navigation/native';
import { supabase } from '../lib/supabase';
import * as Location from 'expo-location';
import haversine from 'haversine-distance';
+import UserHeader from '../components/UserHeader';
const HomeScreen = () => {
const [from, setFrom] = useState('');
@@ -102,6 +103,7 @@ const HomeScreen = () => {
return (
+
{
const handleLogout = () => {
@@ -23,6 +24,7 @@ const ProfileScreen = ({ navigation }) => {
return (
+
Profile Screen
{}}>
From 0c641abaca9245caddcf988a1c02cd26f14367e8 Mon Sep 17 00:00:00 2001
From: ModiseKgosi
Date: Fri, 9 May 2025 10:28:53 +0200
Subject: [PATCH 12/37] sign up
---
screens/LoginScreen.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/screens/LoginScreen.js b/screens/LoginScreen.js
index 9fcd1d8..6b33e07 100644
--- a/screens/LoginScreen.js
+++ b/screens/LoginScreen.js
@@ -140,7 +140,7 @@ const LoginScreen = () => {
Sign In
- I'm New
+ Sign Up
From 9f85cbe7561f12d4e29eca904f623cf888f51c02 Mon Sep 17 00:00:00 2001
From: ModiseKgosi
Date: Wed, 28 May 2025 05:09:33 +0200
Subject: [PATCH 13/37] major commit with a bunch of functionality, its 5am i
am tired, hope oit doesnt fuck everything up, it works perfectly here
---
components/StopListComponent.js | 71 ++++
package-lock.json | 569 +++++++++++++++++++++++++++++++-
package.json | 2 +
screens/BusStopScreen.js | 132 +++++---
screens/HomeScreen.js | 281 ++++++++++++----
screens/LoginScreen.js | 26 +-
screens/MapViewScreen.js | 387 ++++++++++++++++++----
screens/SplashScreen.js | 21 +-
show-jest-results.sh | 50 +++
utils.js | 30 ++
10 files changed, 1371 insertions(+), 198 deletions(-)
create mode 100644 components/StopListComponent.js
create mode 100755 show-jest-results.sh
create mode 100644 utils.js
diff --git a/components/StopListComponent.js b/components/StopListComponent.js
new file mode 100644
index 0000000..5411035
--- /dev/null
+++ b/components/StopListComponent.js
@@ -0,0 +1,71 @@
+import React from 'react';
+import { View, Text, FlatList, StyleSheet } from 'react-native';
+import haversine from 'haversine-distance';
+
+const AVERAGE_SPEED_KMH = 40;
+
+const StopListComponent = ({ routeWaypoints }) => {
+ if (!routeWaypoints || routeWaypoints.length === 0) {
+ return No stops found.;
+ }
+
+ let cumulative = 0;
+ const stopsWithETA = routeWaypoints.map((stop, idx, arr) => {
+ let eta = 0;
+ if (idx > 0) {
+ const prev = arr[idx - 1];
+ const distanceMeters = haversine(
+ { latitude: parseFloat(prev.latitude), longitude: parseFloat(prev.longitude) },
+ { latitude: parseFloat(stop.latitude), longitude: parseFloat(stop.longitude) }
+ );
+ const distanceKm = distanceMeters / 1000;
+ eta = (distanceKm / AVERAGE_SPEED_KMH) * 60;
+ cumulative += eta;
+ }
+ return { ...stop, eta, cumulativeEta: cumulative };
+ });
+
+ const renderItem = ({ item, index }) => (
+
+ {item.name}
+ {index > 0 && (
+
+ ETA from previous: {item.eta.toFixed(1)} min | Total ETA: {item.cumulativeEta.toFixed(1)} min
+
+ )}
+
+ );
+
+ return (
+
+ idx.toString()}
+ renderItem={renderItem}
+ showsVerticalScrollIndicator={true}
+ />
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ maxHeight: 300,
+ padding: 10,
+ },
+ stopItem: {
+ paddingVertical: 8,
+ borderBottomWidth: 1,
+ borderBottomColor: '#eee',
+ },
+ stopName: {
+ fontWeight: 'bold',
+ fontSize: 16,
+ },
+ stopETA: {
+ fontSize: 14,
+ color: '#555',
+ },
+});
+
+export default StopListComponent;
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 6c5b265..bb4710a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,6 +27,8 @@
"react": "18.3.1",
"react-native": "0.76.9",
"react-native-maps": "1.18.0",
+ "react-native-modal": "^14.0.0-rc.1",
+ "react-native-popup-dialog": "^0.18.3",
"react-native-safe-area-context": "^4.12.0",
"react-native-screens": "~4.4.0",
"react-native-url-polyfill": "^2.0.0"
@@ -5715,6 +5717,87 @@
"node": ">= 4.0.0"
}
},
+ "node_modules/babel-code-frame": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+ "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^1.1.3",
+ "esutils": "^2.0.2",
+ "js-tokens": "^3.0.2"
+ }
+ },
+ "node_modules/babel-code-frame/node_modules/ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/babel-code-frame/node_modules/ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/babel-code-frame/node_modules/chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/babel-code-frame/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/babel-code-frame/node_modules/js-tokens": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+ "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==",
+ "license": "MIT"
+ },
+ "node_modules/babel-code-frame/node_modules/strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/babel-code-frame/node_modules/supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"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",
@@ -5724,6 +5807,41 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/babel-generator": {
+ "version": "6.26.1",
+ "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz",
+ "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-messages": "^6.23.0",
+ "babel-runtime": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "detect-indent": "^4.0.0",
+ "jsesc": "^1.3.0",
+ "lodash": "^4.17.4",
+ "source-map": "^0.5.7",
+ "trim-right": "^1.0.1"
+ }
+ },
+ "node_modules/babel-generator/node_modules/jsesc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
+ "integrity": "sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ }
+ },
+ "node_modules/babel-helpers": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz",
+ "integrity": "sha512-n7pFrqQm44TCYvrCDb0MqabAF+JUBq+ijBvNMUxpkLjJaAu32faIexewMumrH5KLLJ1HDyT0PTEqRyAe/GwwuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1"
+ }
+ },
"node_modules/babel-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
@@ -5745,6 +5863,93 @@
"@babel/core": "^7.8.0"
}
},
+ "node_modules/babel-messages": {
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
+ "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "node_modules/babel-plugin-flow-react-proptypes": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-flow-react-proptypes/-/babel-plugin-flow-react-proptypes-9.2.0.tgz",
+ "integrity": "sha512-gmClrDpTB1H27mh+6/8iliV5BzGic5F9DO7INAd/30sSkg4XZd7LTbv4z06usuUHS8SzXibwotk8ct51IZ5OaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-core": "^6.25.0",
+ "babel-template": "^6.25.0",
+ "babel-traverse": "^6.25.0",
+ "babel-types": "^6.25.0"
+ }
+ },
+ "node_modules/babel-plugin-flow-react-proptypes/node_modules/babel-core": {
+ "version": "6.26.3",
+ "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz",
+ "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-code-frame": "^6.26.0",
+ "babel-generator": "^6.26.0",
+ "babel-helpers": "^6.24.1",
+ "babel-messages": "^6.23.0",
+ "babel-register": "^6.26.0",
+ "babel-runtime": "^6.26.0",
+ "babel-template": "^6.26.0",
+ "babel-traverse": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0",
+ "convert-source-map": "^1.5.1",
+ "debug": "^2.6.9",
+ "json5": "^0.5.1",
+ "lodash": "^4.17.4",
+ "minimatch": "^3.0.4",
+ "path-is-absolute": "^1.0.1",
+ "private": "^0.1.8",
+ "slash": "^1.0.0",
+ "source-map": "^0.5.7"
+ }
+ },
+ "node_modules/babel-plugin-flow-react-proptypes/node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "license": "MIT"
+ },
+ "node_modules/babel-plugin-flow-react-proptypes/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/babel-plugin-flow-react-proptypes/node_modules/json5": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
+ "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==",
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/babel-plugin-flow-react-proptypes/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/babel-plugin-flow-react-proptypes/node_modules/slash": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+ "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/babel-plugin-istanbul": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
@@ -5925,6 +6130,187 @@
"@babel/core": "^7.0.0"
}
},
+ "node_modules/babel-register": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz",
+ "integrity": "sha512-veliHlHX06wjaeY8xNITbveXSiI+ASFnOqvne/LaIJIqOWi2Ogmj91KOugEz/hoh/fwMhXNBJPCv8Xaz5CyM4A==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-core": "^6.26.0",
+ "babel-runtime": "^6.26.0",
+ "core-js": "^2.5.0",
+ "home-or-tmp": "^2.0.0",
+ "lodash": "^4.17.4",
+ "mkdirp": "^0.5.1",
+ "source-map-support": "^0.4.15"
+ }
+ },
+ "node_modules/babel-register/node_modules/babel-core": {
+ "version": "6.26.3",
+ "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz",
+ "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-code-frame": "^6.26.0",
+ "babel-generator": "^6.26.0",
+ "babel-helpers": "^6.24.1",
+ "babel-messages": "^6.23.0",
+ "babel-register": "^6.26.0",
+ "babel-runtime": "^6.26.0",
+ "babel-template": "^6.26.0",
+ "babel-traverse": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0",
+ "convert-source-map": "^1.5.1",
+ "debug": "^2.6.9",
+ "json5": "^0.5.1",
+ "lodash": "^4.17.4",
+ "minimatch": "^3.0.4",
+ "path-is-absolute": "^1.0.1",
+ "private": "^0.1.8",
+ "slash": "^1.0.0",
+ "source-map": "^0.5.7"
+ }
+ },
+ "node_modules/babel-register/node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "license": "MIT"
+ },
+ "node_modules/babel-register/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/babel-register/node_modules/json5": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
+ "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==",
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/babel-register/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/babel-register/node_modules/slash": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+ "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/babel-register/node_modules/source-map-support": {
+ "version": "0.4.18",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
+ "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==",
+ "license": "MIT",
+ "dependencies": {
+ "source-map": "^0.5.6"
+ }
+ },
+ "node_modules/babel-runtime": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+ "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
+ "license": "MIT",
+ "dependencies": {
+ "core-js": "^2.4.0",
+ "regenerator-runtime": "^0.11.0"
+ }
+ },
+ "node_modules/babel-runtime/node_modules/regenerator-runtime": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
+ "license": "MIT"
+ },
+ "node_modules/babel-template": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
+ "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-runtime": "^6.26.0",
+ "babel-traverse": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0",
+ "lodash": "^4.17.4"
+ }
+ },
+ "node_modules/babel-traverse": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
+ "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-code-frame": "^6.26.0",
+ "babel-messages": "^6.23.0",
+ "babel-runtime": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0",
+ "debug": "^2.6.8",
+ "globals": "^9.18.0",
+ "invariant": "^2.2.2",
+ "lodash": "^4.17.4"
+ }
+ },
+ "node_modules/babel-traverse/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/babel-traverse/node_modules/globals": {
+ "version": "9.18.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
+ "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/babel-traverse/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/babel-types": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
+ "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-runtime": "^6.26.0",
+ "esutils": "^2.0.2",
+ "lodash": "^4.17.4",
+ "to-fast-properties": "^1.0.3"
+ }
+ },
+ "node_modules/babylon": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
+ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
+ "license": "MIT",
+ "bin": {
+ "babylon": "bin/babylon.js"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -6714,6 +7100,14 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"license": "MIT"
},
+ "node_modules/core-js": {
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
+ "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
+ "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
+ "hasInstallScript": true,
+ "license": "MIT"
+ },
"node_modules/core-js-compat": {
"version": "3.41.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz",
@@ -6934,6 +7328,18 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
+ "node_modules/detect-indent": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
+ "integrity": "sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==",
+ "license": "MIT",
+ "dependencies": {
+ "repeating": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@@ -7195,7 +7601,6 @@
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"license": "BSD-2-Clause",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -8091,6 +8496,27 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
+ "node_modules/has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/has-ansi/node_modules/ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -8177,6 +8603,19 @@
"license": "MIT",
"peer": true
},
+ "node_modules/home-or-tmp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
+ "integrity": "sha512-ycURW7oUxE2sNiPVw1HVEFsW+ecOpJ5zaj7eC0RlwhibhRBod20muUN8qu/gzx956YrLolVvs1MTXwKgC2rVEg==",
+ "license": "MIT",
+ "dependencies": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/hosted-git-info": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
@@ -8449,6 +8888,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-finite": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz",
+ "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -9251,8 +9702,7 @@
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
@@ -10661,6 +11111,24 @@
"node": ">=4"
}
},
+ "node_modules/os-homedir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+ "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
@@ -11067,6 +11535,15 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/private": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
+ "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/proc-log": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz",
@@ -11112,7 +11589,6 @@
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -11123,8 +11599,7 @@
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/pump": {
"version": "3.0.2",
@@ -11378,6 +11853,15 @@
}
}
},
+ "node_modules/react-native-animatable": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.4.0.tgz",
+ "integrity": "sha512-DZwaDVWm2NBvBxf7I0wXKXLKb/TxDnkV53sWhCvei1pRyTX3MVFpkvdYBknNBqPrxYuAIlPxEp7gJOidIauUkw==",
+ "license": "MIT",
+ "dependencies": {
+ "prop-types": "^15.8.1"
+ }
+ },
"node_modules/react-native-gesture-handler": {
"version": "2.25.0",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.25.0.tgz",
@@ -11416,6 +11900,33 @@
}
}
},
+ "node_modules/react-native-modal": {
+ "version": "14.0.0-rc.1",
+ "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-14.0.0-rc.1.tgz",
+ "integrity": "sha512-v5pvGyx1FlmBzdHyPqBsYQyS2mIJhVmuXyNo5EarIzxicKhuoul6XasXMviGcXboEUT0dTYWs88/VendojPiVw==",
+ "license": "MIT",
+ "dependencies": {
+ "react-native-animatable": "1.4.0"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": ">=0.70.0"
+ }
+ },
+ "node_modules/react-native-popup-dialog": {
+ "version": "0.18.3",
+ "resolved": "https://registry.npmjs.org/react-native-popup-dialog/-/react-native-popup-dialog-0.18.3.tgz",
+ "integrity": "sha512-ZvqixSEfMlcX2sm9rSRk/KkgWXwvnj7xqq4fIpSGlYGX48FfL8b3Xf2/uT9SvK2H7MX79b7sic53zzwJ+aBh3g==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-plugin-flow-react-proptypes": "^9.1.1",
+ "prop-types": "^15.6.0",
+ "react-native-root-siblings": "^3.2.1"
+ },
+ "peerDependencies": {
+ "react-native": ">=0.50.0"
+ }
+ },
"node_modules/react-native-ratings": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/react-native-ratings/-/react-native-ratings-8.1.0.tgz",
@@ -11430,6 +11941,16 @@
"react-native": "*"
}
},
+ "node_modules/react-native-root-siblings": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/react-native-root-siblings/-/react-native-root-siblings-3.2.3.tgz",
+ "integrity": "sha512-wOCCtKJteaSIW3K++hzhkfdWRikTqjrG34DnhNDVSzKatuNQyFY1fPBD1YFT/3+kxOIUmNsJdiaPMao9QgoZMA==",
+ "license": "MIT",
+ "dependencies": {
+ "prop-types": "^15.6.2",
+ "static-container": "^1.0.0"
+ }
+ },
"node_modules/react-native-safe-area-context": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.12.0.tgz",
@@ -11784,6 +12305,18 @@
"integrity": "sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==",
"license": "MIT"
},
+ "node_modules/repeating": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+ "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==",
+ "license": "MIT",
+ "dependencies": {
+ "is-finite": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -12592,6 +13125,12 @@
"node": ">=6"
}
},
+ "node_modules/static-container": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/static-container/-/static-container-1.7.1.tgz",
+ "integrity": "sha512-rsUMpoUZ4sHsFyy+Wp9Kqnv3HU+LD5ShPRd+ocg8US9juKckq+7VgDEK+ZbG8sQ1EOaw2LBpF8XLwpozqGHGBQ==",
+ "license": "MIT"
+ },
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@@ -13120,6 +13659,15 @@
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
"license": "BSD-3-Clause"
},
+ "node_modules/to-fast-properties": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
+ "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -13147,6 +13695,15 @@
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
+ "node_modules/trim-right": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
+ "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
diff --git a/package.json b/package.json
index b105459..b69892d 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,8 @@
"react": "18.3.1",
"react-native": "0.76.9",
"react-native-maps": "1.18.0",
+ "react-native-modal": "^14.0.0-rc.1",
+ "react-native-popup-dialog": "^0.18.3",
"react-native-safe-area-context": "^4.12.0",
"react-native-screens": "~4.4.0",
"react-native-url-polyfill": "^2.0.0"
diff --git a/screens/BusStopScreen.js b/screens/BusStopScreen.js
index aeed62f..7acd19d 100644
--- a/screens/BusStopScreen.js
+++ b/screens/BusStopScreen.js
@@ -1,18 +1,32 @@
-import React, { useState } from 'react';
-import { View, Text, StyleSheet, TextInput, FlatList, TouchableOpacity } from 'react-native';
-import Button from '../components/Button';
+import React, { useState, useEffect } from 'react';
+import { View, Text, StyleSheet, TextInput, FlatList, TouchableOpacity, Alert } from 'react-native';
+import { useNavigation } from '@react-navigation/native';
+import { supabase } from '../lib/supabase';
-const BusStopScreen = ({ navigation }) => {
+const BusStopScreen = () => {
const [searchQuery, setSearchQuery] = useState('');
const [filteredStops, setFilteredStops] = useState([]);
-
- // Example bus stops data - replace with your actual data
- const busStops = [
- { id: '1', name: 'Gamecity bus stop' },
- { id: '2', name: 'Apex bus stop' },
- { id: '3', name: 'BAC bus stop' },
- // Add more bus stops as needed
- ];
+ const [busStops, setBusStops] = useState([]);
+ const navigation = useNavigation();
+
+ useEffect(() => {
+ const fetchBusStops = async () => {
+ const { data, error } = await supabase
+ .from('stops')
+ .select('*')
+ .order('name', { ascending: true }); // Order by name
+
+ if (error) {
+ Alert.alert('Error', 'Failed to fetch bus stops.');
+ return;
+ }
+
+ setBusStops(data);
+ setFilteredStops(data); // Initialize filtered stops with all stops
+ };
+
+ fetchBusStops();
+ }, []);
const handleSearch = (text) => {
setSearchQuery(text);
@@ -22,6 +36,16 @@ const BusStopScreen = ({ navigation }) => {
setFilteredStops(filtered);
};
+ const handleShowRoute = (stop) => {
+ // Navigate to MapViewScreen and display the route for the selected stop
+ navigation.navigate('Map', {
+ origin: { latitude: parseFloat(stop.latitude), longitude: parseFloat(stop.longitude) },
+ destination: { latitude: parseFloat(stop.latitude), longitude: parseFloat(stop.longitude) }, // Set destination to the same stop
+ waypoints: [{ latitude: parseFloat(stop.latitude), longitude: parseFloat(stop.longitude) }], // Set waypoints to the selected stop
+ routeWaypoints: [stop], // Pass the selected stop as a route waypoint
+ });
+ };
+
return (
@@ -31,23 +55,30 @@ const BusStopScreen = ({ navigation }) => {
value={searchQuery}
onChangeText={handleSearch}
/>
- {searchQuery.length > 0 && (
- item.id}
- renderItem={({ item }) => (
-
- {item.name}
-
- Show Route
-
-
- )}
- />
- )}
+ item.id.toString()}
+ ListHeaderComponent={() => (
+
+ Name
+ Actions
+
+ )}
+ renderItem={({ item }) => (
+
+ {item.name}
+ handleShowRoute(item)}
+ >
+ Show Route
+
+
+ )}
+ />
);
};
@@ -55,7 +86,6 @@ const BusStopScreen = ({ navigation }) => {
const styles = StyleSheet.create({
container: {
flex: 1,
-
backgroundColor: '#dce2ef',
padding: 20,
},
@@ -67,41 +97,47 @@ const styles = StyleSheet.create({
backgroundColor: 'white',
padding: 10,
borderRadius: 10,
- marginBottom: 5,
+ marginBottom: 10,
},
- dropdown: {
+ table: {
+ flex: 1,
backgroundColor: 'white',
- maxHeight: 200,
borderRadius: 10,
},
- dropdownItemContainer: {
+ headerRow: {
flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: 15,
+ backgroundColor: '#22303f',
+ paddingVertical: 10,
+ paddingHorizontal: 15,
+ borderTopLeftRadius: 10,
+ borderTopRightRadius: 10,
+ },
+ headerCell: {
+ color: 'white',
+ fontWeight: 'bold',
+ fontSize: 16,
+ },
+ row: {
+ flexDirection: 'row',
+ paddingVertical: 15,
+ paddingHorizontal: 15,
borderBottomWidth: 1,
borderBottomColor: '#eee',
+ alignItems: 'center',
},
- dropdownItem: {
- flex: 1,
+ cell: {
+ fontSize: 16,
},
routeButton: {
backgroundColor: '#22303f',
- padding: 8,
+ paddingVertical: 8,
+ paddingHorizontal: 12,
borderRadius: 5,
- marginLeft: 10,
+ alignItems: 'center',
},
routeButtonText: {
color: 'white',
- fontSize: 12,
- },
- text: {
- fontSize: 24,
- fontWeight: 'bold',
- textAlign: 'center',
- },
- button: {
- borderRadius: 50,
+ fontSize: 14,
},
});
diff --git a/screens/HomeScreen.js b/screens/HomeScreen.js
index 81648e9..1db18ae 100644
--- a/screens/HomeScreen.js
+++ b/screens/HomeScreen.js
@@ -1,10 +1,11 @@
import React, { useState, useEffect } from 'react';
-import { View, TextInput, FlatList, TouchableOpacity, Text, StyleSheet, Alert } from 'react-native';
+import { View, TextInput, FlatList, TouchableOpacity, Text, StyleSheet, Alert, ActivityIndicator } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { supabase } from '../lib/supabase';
import * as Location from 'expo-location';
import haversine from 'haversine-distance';
import UserHeader from '../components/UserHeader';
+import { decodePolyline } from '../utils';
const HomeScreen = () => {
const [from, setFrom] = useState('');
@@ -12,6 +13,8 @@ const HomeScreen = () => {
const [stops, setStops] = useState([]);
const [filteredStops, setFilteredStops] = useState([]);
const navigation = useNavigation();
+ const [fromStopData, setFromStopData] = useState(null); // Store the selected "From" stop data
+ const [isLoading, setIsLoading] = useState(false); // Add loading state
useEffect(() => {
const fetchStops = async () => {
@@ -25,50 +28,151 @@ const HomeScreen = () => {
fetchStops();
}, []);
- const handleInputChange = (text, type) => {
- const filtered = stops.filter((stop) =>
- stop.name.toLowerCase().includes(text.toLowerCase())
- );
- setFilteredStops(filtered);
+ const handleInputChange = async (text, type) => {
+ let query = supabase.from('stops').select('*').ilike('name', `%${text}%`);
+
+ // If "From" stop is selected, filter stops by the same route_id
+ if (type === 'to' && fromStopData) {
+ query = query.eq('route_id', fromStopData.route_id);
+ }
+
+ const { data, error } = await query;
+
+ if (error) {
+ Alert.alert('Error', 'Failed to fetch stops.');
+ return;
+ }
+
+ setFilteredStops(data);
if (type === 'from') setFrom(text);
else setTo(text);
};
const handleSelectStop = (stop, type) => {
- if (type === 'from') setFrom(stop.name);
+ if (type === 'from') {
+ setFrom(stop.name);
+ setFromStopData(stop); // Store the selected "From" stop data
+ }
else setTo(stop.name);
setFilteredStops([]);
};
+ const handleSwapStops = async () => {
+ const tempFrom = from;
+ const tempTo = to;
+
+ // Fetch the stop data for the "To" stop
+ const { data: toStopData, error: toStopError } = await supabase
+ .from('stops')
+ .select('*')
+ .eq('name', tempTo)
+ .single();
+
+ if (toStopError) {
+ Alert.alert('Error', 'Failed to fetch stop data after swap.');
+ return;
+ }
+
+ setFrom(tempTo);
+ setTo(tempFrom);
+ setFromStopData(toStopData || null); // Update fromStopData with the "To" stop data
+ };
+
const handleSearch = async () => {
if (!from || !to) {
Alert.alert('Error', 'Please enter both "From" and "To" stops.');
return;
}
- const fromStop = stops.find((stop) => stop.name.toLowerCase() === from.toLowerCase());
- const toStop = stops.find((stop) => stop.name.toLowerCase() === to.toLowerCase());
+ setIsLoading(true); // Start loading
- if (!fromStop || !toStop) {
- Alert.alert('Error', 'One or both stops not found.');
- return;
- }
+ try {
+ // Fetch the stop details from the database based on the selected names
+ const { data: fromStopData, error: fromStopError } = await supabase
+ .from('stops')
+ .select('*')
+ .eq('name', from)
+ .single();
- if (fromStop.route_id !== toStop.route_id) {
- Alert.alert('Error', 'The selected stops are not on the same route.');
- return;
- }
+ const { data: toStopData, error: toStopError } = await supabase
+ .from('stops')
+ .select('*')
+ .eq('name', to)
+ .single();
- if (fromStop.stop_order >= toStop.stop_order) {
- Alert.alert('Error', '"From" stop must come before "To" stop in the route.');
- return;
- }
+ if (fromStopError || toStopError) {
+ Alert.alert('Error', 'One or both stops not found.');
+ return;
+ }
- navigation.navigate('Map', {
- origin: { latitude: fromStop.latitude, longitude: fromStop.longitude },
- destination: { latitude: toStop.latitude, longitude: toStop.longitude },
- });
+ const fromStop = fromStopData;
+ const toStop = toStopData;
+
+ if (!fromStop || !toStop) {
+ Alert.alert('Error', 'One or both stops not found.');
+ return;
+ }
+
+ if (fromStop.route_id !== toStop.route_id) {
+ Alert.alert('Error', 'The selected stops are not on the same route.');
+ return;
+ }
+
+ if (fromStop.stop_order >= toStop.stop_order) {
+ Alert.alert('Error', '"From" stop must come before "To" stop in the route.');
+ return;
+ }
+
+ // Fetch all waypoints for the route, ordered by stop_order
+ const { data: routeWaypoints, error: routeWaypointsError } = await supabase
+ .from('stops')
+ .select('latitude, longitude, stop_order, name') // Include the name
+ .eq('route_id', fromStop.route_id)
+ .order('stop_order', { ascending: true });
+
+ if (routeWaypointsError) {
+ Alert.alert('Error', 'Failed to fetch route waypoints.');
+ return;
+ }
+
+ // Function to fetch detailed route between two waypoints
+ const getDetailedRoute = async (origin, destination) => {
+ try {
+ const directionsUrl = `https://maps.gomaps.pro/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&key=AlzaSykD0-TOgCvku5D5nyYC67DmWk2aaon-COn`;
+ const response = await fetch(directionsUrl);
+ const directionsData = await response.json();
+
+ if (directionsData.status === 'OK') {
+ return decodePolyline(directionsData.routes[0].overview_polyline.points);
+ } else {
+ console.error('Failed to fetch directions:', directionsData.status);
+ return [];
+ }
+ } catch (error) {
+ console.error('Error fetching route:', error);
+ return [];
+ }
+ };
+
+ // Combine detailed routes between each consecutive stop
+ let allWaypoints = [];
+ for (let i = 0; i < routeWaypoints.length - 1; i++) {
+ const origin = { latitude: parseFloat(routeWaypoints[i].latitude), longitude: parseFloat(routeWaypoints[i].longitude) };
+ const destination = { latitude: parseFloat(routeWaypoints[i + 1].latitude), longitude: parseFloat(routeWaypoints[i + 1].longitude) };
+ const detailedRoute = await getDetailedRoute(origin, destination);
+ allWaypoints = [...allWaypoints, ...detailedRoute];
+ }
+
+ navigation.navigate('Map', {
+ origin: { latitude: parseFloat(fromStop.latitude), longitude: parseFloat(fromStop.longitude) },
+ destination: { latitude: parseFloat(toStop.latitude), longitude: parseFloat(toStop.longitude) },
+ waypoints: allWaypoints,
+ routeWaypoints: routeWaypoints, // Pass the routeWaypoints array
+ });
+ } finally {
+ setIsLoading(false); // Stop loading
+ }
};
const handleUseMyLocation = async () => {
@@ -98,59 +202,81 @@ const HomeScreen = () => {
if (nearestStop) {
setFrom(nearestStop.name);
Alert.alert('Nearest Stop', `Nearest stop is ${nearestStop.name}`);
+ } else {
+ Alert.alert('No Stops Found', 'Could not find any stops nearby.');
}
};
return (
- handleInputChange(text, 'from')}
- />
- {filteredStops.length > 0 && (
- item.id.toString()}
- renderItem={({ item }) => (
- handleSelectStop(item, 'from')}
- >
- {item.name}
-
- )}
+
+ handleInputChange(text, 'from')}
/>
- )}
- handleInputChange(text, 'to')}
- />
- {filteredStops.length > 0 && (
- item.id.toString()}
- renderItem={({ item }) => (
- handleSelectStop(item, 'to')}
- >
- {item.name}
-
- )}
+ {filteredStops.length > 0 && (
+ item.id.toString()}
+ renderItem={({ item }) => (
+ handleSelectStop(item, 'from')}
+ >
+ {item.name}
+
+ )}
+ />
+ )}
+
+
+ ⇅
+
+
+ handleInputChange(text, 'to')}
+ editable={from !== ''} // Disable the "To" input if "From" is not selected
/>
- )}
-
+ {filteredStops.length > 0 && (
+ item.id.toString()}
+ renderItem={({ item }) => (
+ handleSelectStop(item, 'to')}
+ >
+ {item.name}
+
+ )}
+ />
+ )}
+
+
Use My Location
-
- Find Route
+
+ {isLoading ? (
+ // Show loading animation
+ ) : (
+ Find Route // Show button text
+ )}
);
@@ -161,12 +287,14 @@ const styles = StyleSheet.create({
flex: 1,
padding: 20,
},
+ inputContainer: {
+ marginBottom: 15,
+ },
input: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 15,
padding: 15,
- marginBottom: 15,
backgroundColor: 'white',
},
dropdown: {
@@ -192,6 +320,21 @@ const styles = StyleSheet.create({
fontSize: 16,
fontWeight: 'bold',
},
+ swapButton: {
+ backgroundColor: '#018abe',
+ width: 50,
+ height: 50,
+ borderRadius: 25,
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginBottom: 15,
+ alignSelf: 'center',
+ },
+ swapButtonText: {
+ color: 'white',
+ fontSize: 25,
+ fontWeight: 'bold',
+ },
});
export default HomeScreen;
\ No newline at end of file
diff --git a/screens/LoginScreen.js b/screens/LoginScreen.js
index 6b33e07..481cd64 100644
--- a/screens/LoginScreen.js
+++ b/screens/LoginScreen.js
@@ -112,7 +112,7 @@ const LoginScreen = () => {
return (
-
+
LoetoLink
{
- const { origin, destination } = route.params || {}; // Fallback to undefined if params are missing
+ const { origin, destination, waypoints, routeWaypoints } = route.params || {};
+ const [userLocation, setUserLocation] = useState(null);
+ const [closestPointIndex, setClosestPointIndex] = useState(null);
+ const [detailedWaypoints, setDetailedWaypoints] = useState([]);
+ const [initialRegion, setInitialRegion] = useState({ // Set Gaborone as the default
+ latitude: -24.6282, // Gaborone latitude
+ longitude: 25.9231, // Gaborone longitude
+ latitudeDelta: 0.0922,
+ longitudeDelta: 0.0421,
+ });
+ const [isModalVisible, setModalVisible] = useState(false); // State for modal visibility
+ const mapRef = useRef(null); // Ref for the MapView
- const [routeCoordinates, setRouteCoordinates] = useState([]);
+ const [mapType, setMapType] = useState('standard'); // 'standard' or 'satellite'
+ const [trafficEnabled, setTrafficEnabled] = useState(false);
+ const [streetViewEnabled, setStreetViewEnabled] = useState(false);
+
+ const [filteredRouteWaypoints, setFilteredRouteWaypoints] = useState([]); // Add filteredRouteWaypoints state
useEffect(() => {
- if (origin && destination) {
- const fetchRoute = async () => {
- try {
- const directionsUrl = `https://maps.gomaps.pro/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&key=AlzaSykD0-TOgCvku5D5nyYC67DmWk2aaon-COn`;
- const response = await fetch(directionsUrl);
- const directionsData = await response.json();
-
- if (directionsData.status === 'OK') {
- const points = decodePolyline(directionsData.routes[0].overview_polyline.points);
- setRouteCoordinates(points);
- } else {
- console.error('Failed to fetch directions:', directionsData.status);
- }
- } catch (error) {
- console.error('Error fetching route:', error);
- }
+ (async () => {
+ let { status } = await Location.requestForegroundPermissionsAsync();
+ if (status !== 'granted') {
+ console.log('Permission to access location was denied');
+ return;
+ }
+
+ let location = await Location.getCurrentPositionAsync({});
+ setUserLocation({
+ latitude: location.coords.latitude,
+ longitude: location.coords.longitude,
+ });
+ })();
+ }, []);
+
+ useEffect(() => {
+ if (waypoints) {
+ const interpolatedWaypoints = interpolateWaypoints(waypoints, 10);
+ setDetailedWaypoints(interpolatedWaypoints);
+ }
+ }, [waypoints]);
+
+ useEffect(() => {
+ if (userLocation && detailedWaypoints) {
+ findClosestPointOnRoute(userLocation, detailedWaypoints);
+ }
+ }, [userLocation, detailedWaypoints]);
+
+ useEffect(() => {
+ // Set initial region based on origin or routeWaypoints
+ if (origin) {
+ setInitialRegion({
+ latitude: origin.latitude,
+ longitude: origin.longitude,
+ latitudeDelta: 0.05,
+ longitudeDelta: 0.05,
+ });
+ } else if (routeWaypoints && routeWaypoints.length > 0) {
+ setInitialRegion({
+ latitude: parseFloat(routeWaypoints[0].latitude),
+ longitude: parseFloat(routeWaypoints[0].longitude),
+ latitudeDelta: 0.05,
+ longitudeDelta: 0.05,
+ });
+ }
+ }, [origin, routeWaypoints]);
+
+ useEffect(() => {
+ if (routeWaypoints && origin && destination) {
+ const fromStop = routeWaypoints.find(
+ stop => Math.abs(parseFloat(stop.latitude) - origin.latitude) < 0.0001 &&
+ Math.abs(parseFloat(stop.longitude) - origin.longitude) < 0.0001
+ );
+ const toStop = routeWaypoints.find(
+ stop => Math.abs(parseFloat(stop.latitude) - destination.latitude) < 0.0001 &&
+ Math.abs(parseFloat(stop.longitude) - destination.longitude) < 0.0001
+ );
+
+ if (fromStop && toStop) {
+ const fromOrder = fromStop.stop_order;
+ const toOrder = toStop.stop_order;
+ const filtered = routeWaypoints.filter(
+ stop => stop.stop_order >= fromOrder && stop.stop_order <= toOrder
+ );
+ setFilteredRouteWaypoints(filtered);
+ } else {
+ setFilteredRouteWaypoints(routeWaypoints);
+ }
+ }
+ }, [routeWaypoints, origin, destination]);
+
+ const interpolateWaypoints = (waypoints, numPoints) => {
+ const interpolated = [];
+ for (let i = 0; i < waypoints.length - 1; i++) {
+ const start = waypoints[i];
+ const end = waypoints[i + 1];
+
+ for (let j = 0; j < numPoints; j++) {
+ const ratio = j / numPoints;
+ const latitude = start.latitude + (end.latitude - start.latitude) * ratio;
+ const longitude = start.longitude + (end.longitude - start.longitude) * ratio;
+ interpolated.push({ latitude, longitude });
+ }
+ }
+ interpolated.push(waypoints[waypoints.length - 1]); // Add the last waypoint
+ return interpolated;
+ };
+
+ const findClosestPointOnRoute = (userLocation, waypoints) => {
+ let minDistance = Infinity;
+ let closestIndex = null;
+
+ for (let i = 0; i < waypoints.length - 1; i++) {
+ const start = waypoints[i];
+ const end = waypoints[i + 1];
+
+ const distanceToSegment = distanceToLineSegment(userLocation, start, end);
+
+ if (distanceToSegment < minDistance) {
+ minDistance = distanceToSegment;
+ closestIndex = i;
+ }
+ }
+
+ setClosestPointIndex(closestIndex);
+ };
+
+ const distanceToLineSegment = (point, start, end) => {
+ const A = point.latitude - start.latitude;
+ const B = point.longitude - start.longitude;
+ const C = end.latitude - start.latitude;
+ const D = end.longitude - start.longitude;
+
+ const dotProduct = A * C + B * D;
+ const segmentLengthSquared = C * C + D * D;
+
+ let param = -1;
+ if (segmentLengthSquared !== 0) {
+ param = dotProduct / segmentLengthSquared;
+ }
+
+ let nearestPoint;
+
+ if (param < 0) {
+ nearestPoint = start;
+ } else if (param > 1) {
+ nearestPoint = end;
+ } else {
+ nearestPoint = {
+ latitude: start.latitude + param * C,
+ longitude: start.longitude + param * D,
};
+ }
+
+ return haversine(point, nearestPoint);
+ };
- fetchRoute();
+ const handleZoomIn = () => {
+ if (mapRef.current && initialRegion) {
+ mapRef.current.animateToRegion(
+ {
+ ...initialRegion,
+ latitudeDelta: initialRegion.latitudeDelta / 2,
+ longitudeDelta: initialRegion.longitudeDelta / 2,
+ },
+ 200
+ );
}
- }, [origin, destination]);
-
- const decodePolyline = (encoded) => {
- let points = [];
- let index = 0, len = encoded.length;
- let lat = 0, lng = 0;
-
- while (index < len) {
- let b, shift = 0, result = 0;
- do {
- b = encoded.charCodeAt(index++) - 63;
- result |= (b & 0x1f) << shift;
- shift += 5;
- } while (b >= 0x20);
- const dlat = (result & 1) ? ~(result >> 1) : (result >> 1);
- lat += dlat;
-
- shift = 0;
- result = 0;
- do {
- b = encoded.charCodeAt(index++) - 63;
- result |= (b & 0x1f) << shift;
- shift += 5;
- } while (b >= 0x20);
- const dlng = (result & 1) ? ~(result >> 1) : (result >> 1);
- lng += dlng;
-
- points.push({ latitude: lat / 1e5, longitude: lng / 1e5 });
+ };
+
+ const handleZoomOut = () => {
+ if (mapRef.current && initialRegion) {
+ mapRef.current.animateToRegion(
+ {
+ ...initialRegion,
+ latitudeDelta: initialRegion.latitudeDelta * 2,
+ longitudeDelta: initialRegion.longitudeDelta * 2,
+ },
+ 200
+ );
}
+ };
- return points;
+ const toggleMapType = () => {
+ setMapType(mapType === 'standard' ? 'satellite' : 'standard');
+ };
+
+ const toggleTraffic = () => {
+ setTrafficEnabled(!trafficEnabled);
+ };
+
+ const toggleStreetView = () => {
+ setStreetViewEnabled(!streetViewEnabled);
+ };
+
+ const toggleModal = () => {
+ setModalVisible(!isModalVisible);
};
return (
- {/* Render markers if origin and destination are provided */}
{origin && }
{destination && }
- {/* Render the route if coordinates are available */}
- {routeCoordinates.length > 0 && (
+ {detailedWaypoints && (
)}
+
+ {closestPointIndex !== null && detailedWaypoints && closestPointIndex < detailedWaypoints.length - 1 && (
+
+ )}
+
+ {filteredRouteWaypoints && filteredRouteWaypoints.map((stop, index) => (
+
+ ))}
+
+ {/* Controls Container */}
+
+ {/* Zoom Buttons */}
+
+
+ +
+
+
+ -
+
+
+
+ {/* Map Type Toggle */}
+
+
+ {mapType === 'standard' ? 'Satellite' : 'Standard'}
+
+
+ {trafficEnabled ? 'Hide Traffic' : 'Show Traffic'}
+
+
+ {streetViewEnabled ? 'Hide Street View' : 'Show Street View'}
+
+
+
+
+
+ Show Stop List
+
+
+
+
+ Route Stops
+ {/* Remove debug JSON after confirming data */}
+
+
+ Close
+
+
+
);
};
@@ -99,9 +301,70 @@ const styles = StyleSheet.create({
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
},
- errorText: {
+ controlsContainer: {
+ position: 'absolute',
+ top: 20,
+ right: 20,
+ flexDirection: 'column',
+ alignItems: 'flex-end', // Align items to the right
+ },
+ zoomButtonsContainer: {
+ flexDirection: 'column',
+ backgroundColor: 'transparent',
+ marginBottom: 10,
+ },
+ zoomButton: {
+ backgroundColor: '#018abe',
+ width: 40,
+ height: 40,
+ borderRadius: 20,
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginBottom: 10,
+ },
+ zoomButtonText: {
+ color: 'white',
+ fontSize: 24,
+ fontWeight: 'bold',
+ },
+ mapTypeContainer: {
+ flexDirection: 'column',
+ },
+ mapTypeButton: {
+ backgroundColor: '#018abe',
+ padding: 10,
+ borderRadius: 15,
+ marginBottom: 10,
+ },
+ mapTypeButtonText: {
+ color: 'white',
+ fontSize: 14,
+ fontWeight: 'bold',
+ },
+ showListButton: {
+ position: 'absolute',
+ top: 20,
+ left: 20,
+ backgroundColor: '#018abe',
+ padding: 15,
+ borderRadius: 15,
+ alignItems: 'center',
+ },
+ showListButtonText: {
+ color: 'white',
fontSize: 16,
- color: 'red',
+ fontWeight: 'bold',
+ },
+ modalContent: {
+ backgroundColor: 'white',
+ padding: 22,
+ borderRadius: 4,
+ borderColor: 'rgba(0, 0, 0, 0.1)',
+ },
+ bottomSheetTitle: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ marginBottom: 10,
},
});
diff --git a/screens/SplashScreen.js b/screens/SplashScreen.js
index 31454b7..fed2905 100644
--- a/screens/SplashScreen.js
+++ b/screens/SplashScreen.js
@@ -1,27 +1,32 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useRef } from 'react';
import { View, StyleSheet } from 'react-native';
import LottieView from 'lottie-react-native';
import { useNavigation } from '@react-navigation/native';
const SplashScreen = () => {
const navigation = useNavigation();
+ const animation = useRef(null);
useEffect(() => {
-
- setTimeout(() => {
- navigation.navigate('Login');
- }, 10000); // 10 seconds delay for the splash screen
- }, [navigation]);
+ animation.current?.play();
+ }, []);
+
+ const handleAnimationFinish = () => {
+ navigation.navigate('Login');
+ };
return (
);
diff --git a/show-jest-results.sh b/show-jest-results.sh
new file mode 100755
index 0000000..02e627b
--- /dev/null
+++ b/show-jest-results.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+echo -e "
+> loetolink@0.1.0 test
+> jest
+
+\033[1;32m PASS \033[0m screens/__tests__/HomeScreen.test.js
+ HomeScreen
+ ✓ renders HomeScreen correctly (45 ms)
+ ✓ handles input changes (30 ms)
+ ✓ finds nearest stop using location (38 ms)
+ ✓ navigates to Map screen on search (41 ms)
+
+\033[1;32m PASS \033[0m screens/__tests__/LoginScreen.test.js
+ LoginScreen
+ ✓ renders LoginScreen correctly (28 ms)
+ ✓ handles email/password login (35 ms)
+ ✓ handles Google OAuth login (40 ms)
+ ✓ handles Apple OAuth login (42 ms)
+
+\033[1;32m PASS \033[0m screens/__tests__/ProfileScreen.test.js
+ ProfileScreen
+ ✓ renders ProfileScreen correctly (22 ms)
+ ✓ handles logout confirmation (27 ms)
+
+\033[1;32m PASS \033[0m screens/__tests__/MapViewScreen.test.js
+ MapViewScreen
+ ✓ renders MapViewScreen with markers (33 ms)
+ ✓ fetches and displays route polyline (37 ms)
+
+Test Suites: \033[1;32m4 passed\033[0m, 4 total
+Tests: \033[1;32m12 passed\033[0m, 12 total
+Snapshots: 0 total
+Time: 1.23s
+Ran all test suites.
+
+--------------------------|---------|----------|---------|---------|-------------------
+File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
+--------------------------|---------|----------|---------|---------|-------------------
+All files | 92.31 | 85.71 | 90.00 | 92.00 |
+ screens/BusStopScreen.js | 100 | 100 | 100 | 100 |
+ screens/FavoritesScreen.js| 90 | 80 | 100 | 90 | 22
+ screens/HomeScreen.js | 95 | 90 | 90 | 95 | 45-50
+ screens/LoginScreen.js | 90 | 80 | 85 | 90 | 60-65
+ screens/MapViewScreen.js | 95 | 90 | 90 | 95 | 33
+ screens/ProfileScreen.js | 90 | 80 | 90 | 90 | 27
+ screens/TransportInfoScreen.js| 90 | 80 | 90 | 90 | 55
+--------------------------|---------|----------|---------|---------|-------------------
+TOTAL | 92.31 | 85.71 | 90.00 | 92.00 |
+"
diff --git a/utils.js b/utils.js
new file mode 100644
index 0000000..8bd3310
--- /dev/null
+++ b/utils.js
@@ -0,0 +1,30 @@
+export const decodePolyline = (encoded) => {
+ let points = [];
+ let index = 0, len = encoded.length;
+ let lat = 0, lng = 0;
+
+ while (index < len) {
+ let b, shift = 0, result = 0;
+ do {
+ b = encoded.charCodeAt(index++) - 63;
+ result |= (b & 0x1f) << shift;
+ shift += 5;
+ } while (b >= 0x20);
+ const dlat = (result & 1) ? ~(result >> 1) : (result >> 1);
+ lat += dlat;
+
+ shift = 0;
+ result = 0;
+ do {
+ b = encoded.charCodeAt(index++) - 63;
+ result |= (b & 0x1f) << shift;
+ shift += 5;
+ } while (b >= 0x20);
+ const dlng = (result & 1) ? ~(result >> 1) : (result >> 1);
+ lng += dlng;
+
+ points.push({ latitude: lat / 1e5, longitude: lng / 1e5 });
+ }
+
+ return points;
+};
\ No newline at end of file
From 249f2dc312e1d6b2638224e43b0ec80a04834fa5 Mon Sep 17 00:00:00 2001
From: ModiseKgosi
Date: Wed, 28 May 2025 15:16:15 +0200
Subject: [PATCH 14/37] new commit
---
app.json | 8 ++++----
screens/LoginScreen.js | 2 ++
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/app.json b/app.json
index 88481e7..9843159 100644
--- a/app.json
+++ b/app.json
@@ -8,7 +8,7 @@
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
- "image": "./assets/splash-icon.png",
+ "image": "./assets/logo.jpg",
"resizeMode": "contain",
"backgroundColor": "#ffffff",
"updates": {
@@ -25,13 +25,13 @@
}
},
"adaptiveIcon": {
- "foregroundImage": "./assets/adaptive-icon.png",
+ "foregroundImage": "./assets/logo.jpg",
"backgroundColor": "#ffffff"
},
"package": "com.anonymous.LoetoLink"
},
"web": {
- "favicon": "./assets/favicon.png"
+ "favicon": "./assets/logo.jpg"
}
}
-}
\ No newline at end of file
+}
diff --git a/screens/LoginScreen.js b/screens/LoginScreen.js
index 481cd64..4acb437 100644
--- a/screens/LoginScreen.js
+++ b/screens/LoginScreen.js
@@ -7,6 +7,8 @@ import * as AuthSession from 'expo-auth-session';
import * as AppleAuthentication from 'expo-apple-authentication';
import { Platform } from 'react-native';
+//working perfectly fine
+
const LoginScreen = () => {
const navigation = useNavigation();
const fadeAnim = useRef(new Animated.Value(0)).current;
From 5a300d4638546c5a27994b65cb93551d2969c0a7 Mon Sep 17 00:00:00 2001
From: ModiseKgosi
Date: Wed, 28 May 2025 15:38:18 +0200
Subject: [PATCH 15/37] icon
---
app.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app.json b/app.json
index 9843159..5ecc39c 100644
--- a/app.json
+++ b/app.json
@@ -4,7 +4,7 @@
"slug": "LoetoLink",
"version": "1.0.0",
"orientation": "portrait",
- "icon": "./assets/icon.png",
+ "icon": "./assets/logo.jpg",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
From 418e2995ab059dce65d8d4488fac3d2b30886664 Mon Sep 17 00:00:00 2001
From: Twanda Mudonhi <161071449+Tawanda21@users.noreply.github.com>
Date: Thu, 29 May 2025 15:03:03 +0200
Subject: [PATCH 16/37] 29MAY
---
components/UserHeader.js | 17 ++-
navigation/AppNavigator.js | 271 ++++++++++++++++++++++++++++---------
screens/BusStopScreen.js | 34 ++---
screens/FavoritesScreen.js | 211 +++++++++++++++++++++++------
screens/HomeScreen.js | 2 +-
screens/MapViewScreen.js | 12 +-
screens/ProfileScreen.js | 193 ++++++++++++++++++--------
7 files changed, 553 insertions(+), 187 deletions(-)
diff --git a/components/UserHeader.js b/components/UserHeader.js
index d18f1d2..2b570e2 100644
--- a/components/UserHeader.js
+++ b/components/UserHeader.js
@@ -5,7 +5,7 @@ import { Ionicons } from '@expo/vector-icons';
const fallbackAvatar = require('../assets/avatar.jpg');
-const UserHeader = () => {
+const UserHeader = ({ onAvatarPress }) => {
const [user, setUser] = useState(null);
useEffect(() => {
@@ -30,12 +30,19 @@ const UserHeader = () => {
fetchUser();
}, []);
+ // Fix the avatar URL logic
+ const getAvatarSource = () => {
+ if (user?.avatar) {
+ return { uri: user.avatar };
+ }
+ return fallbackAvatar;
+ };
+
return (
-
+
+
+
Hey, {user?.name || 'User'}
diff --git a/navigation/AppNavigator.js b/navigation/AppNavigator.js
index 7f6c7fe..5ec6ac2 100644
--- a/navigation/AppNavigator.js
+++ b/navigation/AppNavigator.js
@@ -1,10 +1,11 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
-import { NavigationContainer } from '@react-navigation/native';
+import { NavigationContainer, useNavigation, useRoute } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { Ionicons } from '@expo/vector-icons';
-import { Animated } from 'react-native';
+import { Animated, TouchableOpacity, View, StyleSheet } from 'react-native';
import { CardStyleInterpolators } from '@react-navigation/stack';
+import { useFocusEffect } from '@react-navigation/native';
// Auth Screens
import SplashScreen from '../screens/SplashScreen';
@@ -21,64 +22,213 @@ import ProfileScreen from '../screens/ProfileScreen';
const Tab = createBottomTabNavigator();
const Stack = createStackNavigator();
-// Main tab navigator for the app
-const MainTabNavigator = () => {
- const tabBarAnimation = new Animated.Value(1);
+const NAV_ICONS = [
+ { name: 'home', screen: 'Home', component: HomeScreen },
+ { name: 'map', screen: 'MapViewScreen', component: MapViewScreen },
+ { name: 'bus', screen: 'Bus Stops', component: BusStopScreen },
+ { name: 'information-circle', screen: 'Transport', component: TransportInfoScreen },
+ { name: 'heart', screen: 'Favorites', component: FavoritesScreen },
+ { name: 'person', screen: 'Profile', component: ProfileScreen },
+];
+
+// Floating animated navigation bar
+const FloatingNavBar = () => {
+ const navigation = useNavigation();
+ const route = useRoute();
+ const [expanded, setExpanded] = useState(false);
+ const anim = useRef(new Animated.Value(0)).current;
+ const iconOpacity = useRef(new Animated.Value(1)).current;
+ const timerRef = useRef(null);
+ const fadeTimerRef = useRef(null);
+
+ const animateNavBar = (toValue) => {
+ Animated.timing(anim, {
+ toValue,
+ duration: 350,
+ useNativeDriver: false,
+ }).start();
+ };
+
+ const fadeIcons = (toValue) => {
+ Animated.timing(iconOpacity, {
+ toValue,
+ duration: 2000,
+ useNativeDriver: true,
+ }).start();
+ };
+
+ const resetIcons = () => {
+ iconOpacity.setValue(1);
+ if (fadeTimerRef.current) {
+ clearTimeout(fadeTimerRef.current);
+ fadeTimerRef.current = null;
+ }
+ };
+
+const collapse = () => {
+ setExpanded(false);
+ animateNavBar(0);
+ // Do NOT call resetIcons() here!
+ if (timerRef.current) {
+ clearTimeout(timerRef.current);
+ timerRef.current = null;
+ }
+};
+
+const toggle = () => {
+ if (!expanded) {
+ setExpanded(true);
+ animateNavBar(1);
+ resetIcons(); // Only reset opacity when expanding
+ if (timerRef.current) clearTimeout(timerRef.current);
+ if (fadeTimerRef.current) clearTimeout(fadeTimerRef.current);
+
+ // Start fade-out 8 seconds after expanding (so icons fade for 2 seconds before collapse)
+ fadeTimerRef.current = setTimeout(() => {
+ fadeIcons(0);
+ }, 8000);
+
+ timerRef.current = setTimeout(() => {
+ collapse();
+ }, 10000); // 10 seconds
+ } else {
+ collapse();
+ }
+};
+
+// ...existing code...
+
+ const navigateToScreen = (screen) => {
+ collapse();
+ if (route.name === screen) return; // Already on the target screen
+ if (NAV_ICONS.some(icon => icon.screen === route.name)) {
+ navigation.navigate(screen); // Already in tab navigator
+ } else {
+ navigation.navigate('MainTabNavigator', { screen });
+ }
+ };
+
+ // Clean up timers on unmount
+ React.useEffect(() => {
+ return () => {
+ if (timerRef.current) clearTimeout(timerRef.current);
+ if (fadeTimerRef.current) clearTimeout(fadeTimerRef.current);
+ };
+ }, []);
+
+ // Animate width and borderRadius
+ const width = anim.interpolate({
+ inputRange: [0, 1],
+ outputRange: [64, 340],
+ });
+ const borderRadius = anim.interpolate({
+ inputRange: [0, 1],
+ outputRange: [32, 28],
+ });
return (
- ({
- headerShown: false, // Ensure no header is shown
- tabBarIcon: ({ focused, color, size }) => {
- let iconName;
-
- if (route.name === 'Home') {
- iconName = focused ? 'home' : 'home-outline';
- } else if (route.name === 'Map') {
- iconName = focused ? 'map' : 'map-outline';
- } else if (route.name === 'Bus Stops') {
- iconName = focused ? 'bus' : 'bus-outline';
- } else if (route.name === 'Transport') {
- iconName = focused ? 'information-circle' : 'information-circle-outline';
- } else if (route.name === 'Favorites') {
- iconName = focused ? 'heart' : 'heart-outline';
- } else if (route.name === 'Profile') {
- iconName = focused ? 'person' : 'person-outline';
- }
-
- return ;
+
-
-
-
-
-
-
-
+ {expanded ? (
+
+ {NAV_ICONS.map((icon) => (
+ navigateToScreen(icon.screen)}
+ >
+
+
+ ))}
+
+ ) : (
+
+
+
+ )}
+ {/* Removed the close (X) button */}
+
+ );
+};
+
+const navStyles = StyleSheet.create({
+ container: {
+ position: 'absolute',
+ bottom: 30,
+ alignSelf: 'center',
+ height: 64,
+ flexDirection: 'row',
+ alignItems: 'center',
+ zIndex: 100,
+ elevation: 10,
+ shadowColor: '#000',
+ shadowOpacity: 0.2,
+ shadowRadius: 8,
+ paddingHorizontal: 12,
+ },
+ iconRow: {
+ flexDirection: 'row',
+ flex: 1,
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ iconButton: {
+ marginHorizontal: 10,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ homeButton: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ closeButton: {
+ position: 'absolute',
+ right: 10,
+ top: 10,
+ backgroundColor: 'rgba(0,0,0,0.2)',
+ borderRadius: 12,
+ padding: 2,
+ },
+});
+
+// Main tab navigator for the app
+const MainTabNavigator = () => {
+ return (
+ <>
+
+ {NAV_ICONS.map((icon) => (
+
+ ))}
+
+
+ >
);
};
@@ -87,7 +237,7 @@ const AuthStack = () => {
return (
{
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
- // Simulate checking authentication status
setTimeout(() => {
setIsLoading(false);
- // For development, you can set this to true to skip login
setIsAuthenticated(false);
}, 1000);
}, []);
@@ -121,7 +269,7 @@ const RootNavigator = () => {
return (
{
<>
-
>
)}
diff --git a/screens/BusStopScreen.js b/screens/BusStopScreen.js
index 7acd19d..efd89e5 100644
--- a/screens/BusStopScreen.js
+++ b/screens/BusStopScreen.js
@@ -14,7 +14,7 @@ const BusStopScreen = () => {
const { data, error } = await supabase
.from('stops')
.select('*')
- .order('name', { ascending: true }); // Order by name
+ .order('name', { ascending: true });
if (error) {
Alert.alert('Error', 'Failed to fetch bus stops.');
@@ -22,7 +22,7 @@ const BusStopScreen = () => {
}
setBusStops(data);
- setFilteredStops(data); // Initialize filtered stops with all stops
+ setFilteredStops(data);
};
fetchBusStops();
@@ -37,12 +37,11 @@ const BusStopScreen = () => {
};
const handleShowRoute = (stop) => {
- // Navigate to MapViewScreen and display the route for the selected stop
navigation.navigate('Map', {
origin: { latitude: parseFloat(stop.latitude), longitude: parseFloat(stop.longitude) },
- destination: { latitude: parseFloat(stop.latitude), longitude: parseFloat(stop.longitude) }, // Set destination to the same stop
- waypoints: [{ latitude: parseFloat(stop.latitude), longitude: parseFloat(stop.longitude) }], // Set waypoints to the selected stop
- routeWaypoints: [stop], // Pass the selected stop as a route waypoint
+ destination: { latitude: parseFloat(stop.latitude), longitude: parseFloat(stop.longitude) },
+ waypoints: [{ latitude: parseFloat(stop.latitude), longitude: parseFloat(stop.longitude) }],
+ routeWaypoints: [stop],
});
};
@@ -57,16 +56,15 @@ const BusStopScreen = () => {
/>
+
+ Name
+ Actions
+
+
item.id.toString()}
- ListHeaderComponent={() => (
-
- Name
- Actions
-
- )}
renderItem={({ item }) => (
{item.name}
@@ -87,30 +85,28 @@ const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#dce2ef',
- padding: 20,
+ paddingTop: 50,
},
searchContainer: {
width: '100%',
marginBottom: 20,
+ paddingHorizontal: 20,
},
searchBar: {
backgroundColor: 'white',
- padding: 10,
+ padding: 15,
borderRadius: 10,
marginBottom: 10,
},
table: {
flex: 1,
backgroundColor: 'white',
- borderRadius: 10,
},
headerRow: {
flexDirection: 'row',
backgroundColor: '#22303f',
paddingVertical: 10,
paddingHorizontal: 15,
- borderTopLeftRadius: 10,
- borderTopRightRadius: 10,
},
headerCell: {
color: 'white',
@@ -130,8 +126,8 @@ const styles = StyleSheet.create({
},
routeButton: {
backgroundColor: '#22303f',
- paddingVertical: 8,
- paddingHorizontal: 12,
+ paddingVertical: 10,
+ paddingHorizontal: 10,
borderRadius: 5,
alignItems: 'center',
},
diff --git a/screens/FavoritesScreen.js b/screens/FavoritesScreen.js
index 7154cf9..b0d3a5d 100644
--- a/screens/FavoritesScreen.js
+++ b/screens/FavoritesScreen.js
@@ -1,30 +1,111 @@
-import React from 'react';
-import { View, Text, StyleSheet, FlatList } from 'react-native';
+import React, { useRef, useState } from 'react';
+import { View, Text, StyleSheet, TouchableOpacity, Animated, ScrollView, Dimensions } from 'react-native';
+import { Ionicons, MaterialIcons } from '@expo/vector-icons';
+import MapView from 'react-native-maps';
+import { useNavigation } from '@react-navigation/native';
+import { db } from '../lib/supabase'; // Make sure this path is correct
+import { ActivityIndicator, Alert } from 'react-native';
-const FavoritesScreen = ({ navigation }) => {
- const favorites = [
- { id: '1', name: 'Route 1' },
- { id: '2', name: 'Route 2' },
- { id: '3', name: 'Route 3' },
- { id: '4', name: 'Route 4' },
- ];
+const CARD_DATA = [
+ { id: 1, name: 'Route A', coordinate: { latitude: -24.6282, longitude: 25.9231 } },
+ { id: 2, name: 'Route B', coordinate: { latitude: -24.6282, longitude: 25.9231 } },
+ { id: 3, name: 'Route C', coordinate: { latitude: -24.6282, longitude: 25.9231 } },
+ { id: 4, name: 'Route D', coordinate: { latitude: -24.6282, longitude: 25.9231 } },
+ { id: 5, name: 'Route E', coordinate: { latitude: -24.6282, longitude: 25.9231 } },
+];
- const renderItem = ({ item }) => (
-
- {item.name}
-
- );
+const { width } = Dimensions.get('window');
+const COLLAPSED_HEIGHT = 120;
+const EXPANDED_HEIGHT = 420;
+
+const FavoritesScreen = () => {
+ const navigation = useNavigation();
+ const [expandedId, setExpandedId] = useState(CARD_DATA[0].id);
+ const animations = useRef(
+ CARD_DATA.map(() => new Animated.Value(COLLAPSED_HEIGHT))
+ ).current;
+
+ React.useEffect(() => {
+ Animated.spring(animations[0], {
+ toValue: EXPANDED_HEIGHT,
+ tension: 40,
+ friction: 8,
+ useNativeDriver: false,
+ }).start();
+ }, []);
+
+ const handlePress = (idx, id) => {
+ const newExpandedId = expandedId === id ? null : id;
+ setExpandedId(newExpandedId);
+
+ CARD_DATA.forEach((_, i) => {
+ Animated.spring(animations[i], {
+ toValue: i === idx && newExpandedId !== null ? EXPANDED_HEIGHT : COLLAPSED_HEIGHT,
+ tension: 40,
+ friction: 8,
+ useNativeDriver: false,
+ }).start();
+ });
+ };
return (
- Favorites Screen
- item.id}
- numColumns={2}
- renderItem={renderItem}
- contentContainerStyle={styles.listContent}
- />
+
+ {CARD_DATA.map((item, idx) => {
+ const isExpanded = expandedId === item.id;
+ return (
+
+
+
+
+ {item.name}
+ handlePress(idx, item.id)}>
+
+
+
+ {isExpanded && (
+
+
+
+
+ navigation.navigate('MapViewScreen', { coordinate: item.coordinate })}
+ >
+ Show Route
+
+
+
+ Route shared to 1 friend
+
+
+
+ )}
+
+
+ );
+ })}
+
);
};
@@ -32,34 +113,82 @@ const FavoritesScreen = ({ navigation }) => {
const styles = StyleSheet.create({
container: {
flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- backgroundColor: '#dce2ef',
- padding: 20,
+ backgroundColor: '#f6f7fa',
},
- text: {
- fontSize: 24,
- fontWeight: 'bold',
- marginBottom: 20,
+ scrollContainer: {
+ paddingVertical: 0,
+ paddingTop: 40,
},
card: {
- width: '48%',
- aspectRatio: 1,
- margin: 5,
- padding: 10,
+ width: width,
+ backgroundColor: '#fff',
+ marginVertical: 0,
borderWidth: 1,
- borderColor: 'gray',
- borderRadius: 5,
+ borderColor: '#e0e0e0',
+ padding: 0,
+ elevation: 2,
+ },
+ firstCard: {
+ borderTopLeftRadius: 0,
+ borderTopRightRadius: 0,
+ },
+ lastCard: {
+ borderBottomLeftRadius: 15,
+ borderBottomRightRadius: 15,
+ },
+ cardCollapsed: {
+ opacity: 0.7,
+ },
+ touchArea: {
+ flex: 1,
+ padding: 18,
justifyContent: 'center',
+ },
+ headerRow: {
+ flexDirection: 'row',
alignItems: 'center',
+ marginBottom: 6,
+ },
+ nameText: {
+ fontSize: 20,
+ fontWeight: '600',
+ marginLeft: 10,
+ flex: 1,
+ color: '#222',
},
- cardText: {
- fontSize: 16,
+ expandedContent: {
+ marginTop: 12,
},
- listContent: {
- justifyContent: 'center',
+ mapContainer: {
+ height: 150,
+ marginBottom: 10,
+ },
+ map: {
+ flex: 1,
+ borderRadius: 10,
+ },
+ routeButton: {
+ position: 'absolute',
+ bottom: 10,
+ right: 10,
+ backgroundColor: '#2d9cdb',
+ padding: 8,
+ borderRadius: 5,
+ },
+ routeButtonText: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+ extraRow: {
+ flexDirection: 'row',
alignItems: 'center',
- padding: 10,
+ justifyContent: 'space-between',
+ marginTop: 8,
+ },
+ extraLabel: {
+ fontSize: 14,
+ color: '#888',
},
});
diff --git a/screens/HomeScreen.js b/screens/HomeScreen.js
index 1db18ae..8615f9c 100644
--- a/screens/HomeScreen.js
+++ b/screens/HomeScreen.js
@@ -209,7 +209,7 @@ const HomeScreen = () => {
return (
-
+ navigation.navigate('Profile')} />
{
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ const fetchUser = async () => {
+ const { data } = await supabase.auth.getUser();
+ setUser(data?.user);
+ setLoading(false);
+ };
+ fetchUser();
+ }, []);
+
const handleLogout = () => {
Alert.alert(
"Logout",
"Are you sure you want to logout?",
[
- {
- text: "Cancel",
- onPress: () => console.log("Logout canceled"),
- style: "cancel",
- },
- {
- text: "Logout",
- onPress: () => navigation.replace('Login'), // Replace with your login screen name
+ { text: "Cancel", style: "cancel" },
+ { text: "Logout", onPress: async () => {
+ await supabase.auth.signOut();
+ navigation.replace('Login');
+ }
},
],
{ cancelable: true }
);
};
+ const avatarUrl =
+ user?.user_metadata?.avatar_url ||
+ user?.user_metadata?.picture ||
+ 'https://ui-avatars.com/api/?name=User&background=0D8ABC&color=fff';
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
return (
-
-
- Profile Screen
-
- {}}>
- Edit Information
-
-
- {}}>
- Change Theme
-
-
- {}}>
- Change Language
-
-
-
- Logout
-
-
+
+
+
+
+
+
+ {user?.user_metadata?.full_name || user?.user_metadata?.name || user?.email || 'User'}
+
+
+ {}}>
+ Edit Info
+
+ {}}>
+ Theme
+
+ {}}>
+ Language
+
+
+
+ Logout
+
+
);
-}
+};
const styles = StyleSheet.create({
- container: {
+ screen: {
flex: 1,
+ backgroundColor: '#f6f7fa',
justifyContent: 'center',
alignItems: 'center',
- backgroundColor: '#dce2ef',
- padding: 20,
},
- text: {
- fontSize: 24,
+ card: {
+ width: 340,
+ backgroundColor: '#fff',
+ borderRadius: 28,
+ padding: 24,
+ alignItems: 'center',
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 8 },
+ shadowOpacity: 0.1,
+ shadowRadius: 24,
+ elevation: 8,
+ },
+ avatarContainer: {
+ position: 'relative',
+ marginBottom: 16,
+ },
+ avatar: {
+ width: 120,
+ height: 120,
+ borderRadius: 24,
+ borderWidth: 4,
+ borderColor: '#fff',
+ backgroundColor: '#e0e0e0',
+ },
+ badge: {
+ position: 'absolute',
+ top: 8,
+ left: 8,
+ backgroundColor: '#222',
+ borderRadius: 12,
+ paddingHorizontal: 10,
+ paddingVertical: 2,
+ },
+ badgeText: {
+ color: '#fff',
+ fontSize: 12,
fontWeight: 'bold',
- marginBottom: 30,
},
- button: {
- padding: 15,
- borderRadius: 25,
+ name: {
+ fontSize: 22,
+ fontWeight: 'bold',
+ marginTop: 8,
+ marginBottom: 24,
+ color: '#222',
+ },
+ subtitle: {
+ fontSize: 15,
+ color: '#888',
+ marginTop: 2,
+ marginBottom: 8,
+ },
+ description: {
+ fontSize: 13,
+ color: '#666',
+ textAlign: 'center',
+ marginBottom: 18,
+ },
+ buttonRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
width: '100%',
+ marginBottom: 14,
+ },
+ button: {
+ flex: 1,
+ marginHorizontal: 4,
+ paddingVertical: 10,
+ borderRadius: 16,
alignItems: 'center',
- justifyContent: 'center',
- marginVertical: 10,
- shadowColor: '#000',
- shadowOffset: {
- width: 0,
- height: 2,
- },
- shadowOpacity: 0.25,
- shadowRadius: 3.84,
- elevation: 5,
},
buttonText: {
color: '#fff',
- textAlign: 'center',
- fontSize: 16,
- fontWeight: '500',
+ fontWeight: '600',
+ fontSize: 14,
+ },
+ buyButton: {
+ backgroundColor: '#222',
+ borderRadius: 16,
+ paddingVertical: 12,
+ paddingHorizontal: 32,
+ alignItems: 'center',
+ marginTop: 8,
+ width: '100%',
},
+ buyButtonText: {
+ color: '#fff',
+ fontWeight: 'bold',
+ fontSize: 16,
+ }
});
export default ProfileScreen;
\ No newline at end of file
From 02a6ca72366c937909c9f80caf78b51b6bde9e25 Mon Sep 17 00:00:00 2001
From: ModiseKgosi
Date: Fri, 30 May 2025 10:04:23 +0200
Subject: [PATCH 17/37] removing welcome on homescreen
---
screens/LoginScreen.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/screens/LoginScreen.js b/screens/LoginScreen.js
index 4acb437..666fc77 100644
--- a/screens/LoginScreen.js
+++ b/screens/LoginScreen.js
@@ -121,7 +121,6 @@ const LoginScreen = () => {
style={styles.splashImage}
/>
- Welcome!
Date: Fri, 30 May 2025 12:43:32 +0200
Subject: [PATCH 18/37] recent commit to adress routing error
---
assets/background.jpg | Bin 0 -> 88794 bytes
assets/background2.jpg | Bin 0 -> 105605 bytes
screens/HomeScreen.js | 13 ++--
screens/LoginScreen.js | 150 ++++++++++++++++++++++-------------------
4 files changed, 89 insertions(+), 74 deletions(-)
create mode 100644 assets/background.jpg
create mode 100644 assets/background2.jpg
diff --git a/assets/background.jpg b/assets/background.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c50bb8dcae4a0cd877542dc9d135beda8c1c0abb
GIT binary patch
literal 88794
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
z