diff --git a/app.json b/app.json index d11bd3f..6ca5cf9 100644 --- a/app.json +++ b/app.json @@ -6,7 +6,7 @@ "orientation": "portrait", "icon": "./assets/images/icon.png", "scheme": "batteryasaservice", - "userInterfaceStyle": "automatic", + "userInterfaceStyle": "light", "newArchEnabled": true, "splash": { "image": "./assets/images/splash-icon.png", diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 6e8462b..5748a6d 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -6,22 +6,24 @@ import { } from "react-native"; import { useTranslation } from "react-i18next"; import SemiCircleProgress from "../../components/home/SemiCircleProgress"; -import { Text, View } from "@/components/Themed"; +import { Text, View } from "react-native"; import { useNavigation } from "expo-router"; -import { useLayoutEffect, useState } from "react"; +import { useEffect, useLayoutEffect, useState } from "react"; import { StatusBar } from "expo-status-bar"; -import Profile from "../../components/home/Profile"; import CustomerCareIcon from "../../assets/icons/customer-care.svg"; import ServiceReminderCard from "@/components/home/ServiceReminderCard"; import MetricCard from "@/components/home/MetricCard"; import PaymentDueCard from "@/components/home/PaymentDueCard"; -import MapView, { Marker } from "react-native-maps"; +import MapView, { Marker, PROVIDER_GOOGLE } from "react-native-maps"; import BatteryWarrantyCard from "@/components/home/BatteryWarrantyCars"; import CustomerSupportModal from "@/components/home/CustomerSupportModal"; import { useSelector } from "react-redux"; import { RootState } from "@/store"; import LocationOff from "@/assets/icons/location_off.svg"; import { Linking } from "react-native"; +import { useRouter } from "expo-router"; +import ProfileImage from "@/components/home/Profile"; +import { calculateBearing, calculateDistance } from "@/utils/Map"; export default function HomeScreen() { const { t } = useTranslation(); @@ -30,6 +32,53 @@ export default function HomeScreen() { const { SoC, SoH, chargingState, lat, lon, loading, error } = useSelector( (state: RootState) => state.telemetry ); + const [prevPosition, setPrevPosition] = useState<{ + lat: number; + lon: number; + } | null>(null); + const [bearing, setBearing] = useState(0); + + const step = 0.001; + useEffect(() => { + if (lat && lon) { + if (prevPosition) { + // Calculate bearing between prev and current position + const newBearing = calculateBearing( + prevPosition.lat, + prevPosition.lon, + lat, + lon + ); + setBearing(newBearing); + } + setPrevPosition({ lat, lon }); // Update previous position + } + }, [lat, lon]); + + useEffect(() => { + if (lat && lon) { + if (prevPosition) { + const distance = calculateDistance( + prevPosition.lat, + prevPosition.lon, + lat, + lon + ); + if (distance > 5) { + // Only update bearing if moved >5 meters + const newBearing = calculateBearing( + prevPosition.lat, + prevPosition.lon, + lat, + lon + ); + setBearing(newBearing); + } + } + setPrevPosition({ lat, lon }); + } + }, [lat, lon]); + const router = useRouter(); useLayoutEffect(() => { navigation.setOptions({ @@ -43,11 +92,25 @@ export default function HomeScreen() { setIsSupportModalVisible(true)} + onPress={() => { + console.log("Support Pressed"); + setIsSupportModalVisible(true); + }} > - + { + router.push("/user/profile"); + }} + > + router.push("/user/profile")} + textSize={20} + boxSize={40} + /> + ), }); @@ -97,14 +160,14 @@ export default function HomeScreen() { <> - alert(JSON.stringify(e.nativeEvent.coordinate)) - } - title={"Test Marker"} - description={"This is a description of the marker"} - image={require("../../assets/icons/marker.png")} + tracksViewChanges={false} + image={require("../../assets/images/marker.png")} /> diff --git a/app/(tabs)/my-battery.tsx b/app/(tabs)/my-battery.tsx index d250141..0c5ae04 100644 --- a/app/(tabs)/my-battery.tsx +++ b/app/(tabs)/my-battery.tsx @@ -1,18 +1,11 @@ import { StyleSheet } from "react-native"; -import EditScreenInfo from "@/components/EditScreenInfo"; -import { Text, View } from "@/components/Themed"; +import { Text, View } from "react-native"; export default function MyBatteryTabScreen() { return ( Coming Soon - - ); } diff --git a/app/(tabs)/payments.tsx b/app/(tabs)/payments.tsx index be2abdf..221847c 100644 --- a/app/(tabs)/payments.tsx +++ b/app/(tabs)/payments.tsx @@ -1,18 +1,10 @@ import { StyleSheet } from "react-native"; - -import EditScreenInfo from "@/components/EditScreenInfo"; -import { Text, View } from "@/components/Themed"; +import { Text, View } from "react-native"; export default function PaymentsTabScreen() { return ( Tab Two - - ); } diff --git a/app/(tabs)/service.tsx b/app/(tabs)/service.tsx index 7348c24..a810b5f 100644 --- a/app/(tabs)/service.tsx +++ b/app/(tabs)/service.tsx @@ -1,18 +1,10 @@ import { StyleSheet } from "react-native"; - -import EditScreenInfo from "@/components/EditScreenInfo"; -import { Text, View } from "@/components/Themed"; +import { Text, View } from "react-native"; export default function ServiceTabScreen() { return ( Coming Soon - - ); } diff --git a/app/+not-found.tsx b/app/+not-found.tsx index ffb5643..7a5ce86 100644 --- a/app/+not-found.tsx +++ b/app/+not-found.tsx @@ -1,12 +1,12 @@ -import { Link, Stack } from 'expo-router'; -import { StyleSheet } from 'react-native'; +import { Link, Stack } from "expo-router"; +import { StyleSheet } from "react-native"; -import { Text, View } from '@/components/Themed'; +import { Text, View } from "react-native"; export default function NotFoundScreen() { return ( <> - + This screen doesn't exist. @@ -21,13 +21,13 @@ export default function NotFoundScreen() { const styles = StyleSheet.create({ container: { flex: 1, - alignItems: 'center', - justifyContent: 'center', + alignItems: "center", + justifyContent: "center", padding: 20, }, title: { fontSize: 20, - fontWeight: 'bold', + fontWeight: "bold", }, link: { marginTop: 15, @@ -35,6 +35,6 @@ const styles = StyleSheet.create({ }, linkText: { fontSize: 14, - color: '#2e78b7', + color: "#2e78b7", }, }); diff --git a/app/modal.tsx b/app/modal.tsx deleted file mode 100644 index fcedb7a..0000000 --- a/app/modal.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { StatusBar } from 'expo-status-bar'; -import { Platform, StyleSheet } from 'react-native'; - -import EditScreenInfo from '@/components/EditScreenInfo'; -import { Text, View } from '@/components/Themed'; - -export default function ModalScreen() { - return ( - - Modal - - - - {/* Use a light status bar on iOS to account for the black space above the modal */} - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, - title: { - fontSize: 20, - fontWeight: 'bold', - }, - separator: { - marginVertical: 30, - height: 1, - width: '80%', - }, -}); diff --git a/app/user/_layout.tsx b/app/user/_layout.tsx new file mode 100644 index 0000000..7b70e6f --- /dev/null +++ b/app/user/_layout.tsx @@ -0,0 +1,51 @@ +import { Stack } from "expo-router"; +import { StatusBar } from "expo-status-bar"; +import { View, StyleSheet, TouchableOpacity } from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import GoBack from "@/assets/icons/chevron_left.svg"; +import { useRouter } from "expo-router"; + +export default function AuthLayout() { + const router = useRouter(); + return ( + <> + + + { + return ( + router.back()}> + + + ); + }, + headerStyle: { + backgroundColor: "#F3F5F8", + }, + animation: "slide_from_right", + }} + /> + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "#F3F5F8", + }, +}); diff --git a/app/user/profile.tsx b/app/user/profile.tsx new file mode 100644 index 0000000..c197d85 --- /dev/null +++ b/app/user/profile.tsx @@ -0,0 +1,153 @@ +import React from "react"; +import { + View, + Text, + StyleSheet, + Image, + TouchableOpacity, + ScrollView, +} from "react-native"; +import { MaterialIcons } from "@expo/vector-icons"; + +export default function ProfileScreen() { + return ( + + + + + + + + + + + Name + Amar Kesari + + + + + + + + + Mobile Number + 9876543210 + + + + + {/* Other Menu Items */} + + {menuItem("My Vehicle")} + + {menuItem("Language")} + + + + {menuItem("About App")} + + {menuItem("Logout")} + + + ); +} + +const menuItem = (title: string) => ( + + {title} + + +); + +const styles = StyleSheet.create({ + container: { + borderWidth: 1, + flex: 1, + backgroundColor: "#F3F5F8", + }, + topBar: { + flexDirection: "row", + alignItems: "center", + paddingHorizontal: 8, + paddingVertical: 12, + backgroundColor: "#F3F5F8", + }, + backButton: { + padding: 8, + }, + headerText: { + fontSize: 16, + fontWeight: "600", + marginLeft: 8, + color: "#252A34", + }, + scrollContent: { + paddingHorizontal: 16, + paddingBottom: 32, + }, + avatarContainer: { + alignItems: "center", + marginVertical: 24, + }, + avatar: { + width: 120, + height: 120, + borderRadius: 60, + }, + editAvatar: { + position: "absolute", + bottom: 0, + right: 105, + width: 40, + height: 40, + backgroundColor: "#008866", + borderRadius: 20, + justifyContent: "center", + alignItems: "center", + }, + card: { + backgroundColor: "#FCFCFC", + borderRadius: 8, + marginBottom: 16, + paddingHorizontal: 16, + paddingVertical: 8, + }, + row: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingVertical: 12, + }, + textGroup: { + flexDirection: "column", + }, + label: { + fontSize: 14, + color: "#252A34", + }, + value: { + fontSize: 14, + fontWeight: "600", + color: "#252A34", + marginTop: 2, + }, + divider: { + height: 1, + backgroundColor: "#E5E9F0", + marginVertical: 4, + }, + menuRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingVertical: 16, + }, + menuText: { + fontSize: 14, + color: "#252A34", + }, +}); diff --git a/assets/images/marker.png b/assets/images/marker.png new file mode 100644 index 0000000..ac2bec9 Binary files /dev/null and b/assets/images/marker.png differ diff --git a/assets/images/user_image.jpg b/assets/images/user_image.jpg new file mode 100644 index 0000000..efd2f94 Binary files /dev/null and b/assets/images/user_image.jpg differ diff --git a/components/EditScreenInfo.tsx b/components/EditScreenInfo.tsx deleted file mode 100644 index 9b4a437..0000000 --- a/components/EditScreenInfo.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from "react"; -import { StyleSheet } from "react-native"; - -import { ExternalLink } from "./ExternalLink"; -import { MonoText } from "./StyledText"; -import { Text, View } from "./Themed"; - -import Colors from "@/constants/Colors"; - -export default function EditScreenInfo({ path }: { path: string }) { - return ( - - - - Open up the code for this screen: - - - - {path} - - - - Change any of the text, save the file, and your app will automatically - update. - - - - - - - Tap here if your app doesn't automatically update after making - changes - - - - - ); -} - -const styles = StyleSheet.create({ - getStartedContainer: { - alignItems: "center", - marginHorizontal: 50, - }, - homeScreenFilename: { - marginVertical: 7, - }, - codeHighlightContainer: { - borderRadius: 3, - paddingHorizontal: 4, - }, - getStartedText: { - fontSize: 17, - lineHeight: 24, - textAlign: "center", - }, - helpContainer: { - marginTop: 15, - marginHorizontal: 20, - alignItems: "center", - }, - helpLink: { - paddingVertical: 15, - }, - helpLinkText: { - textAlign: "center", - }, -}); diff --git a/eas.json b/eas.json new file mode 100644 index 0000000..8fd3dd7 --- /dev/null +++ b/eas.json @@ -0,0 +1,16 @@ +{ + "build": { + "development": { + "env": { + "ENV": "development", + "BASE_URL": "https://dev-api-service.vecmocon.com/service-buddy" + } + }, + "production": { + "env": { + "ENV": "production", + "BASE_URL": "https://dev-api-service.vecmocon.com/service-buddy" + } + } + } +} diff --git a/services/socket.ts b/services/socket.ts index fc89595..f7f12f3 100644 --- a/services/socket.ts +++ b/services/socket.ts @@ -10,7 +10,7 @@ import { BmsState } from "@/constants/types"; const SERVER_URL = "http://dev.vec-tr.ai:8089/?dashboardId=deviceDashboardSocket&assetId=V16000868651064644504"; const TOKEN = - "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2NCIsImFjdGlvbiI6ImF1dGgiLCJ0b2tlbi12ZXJzaW9uIjowLCJpYXQiOjE3NTE0NTgxNDQsImV4cCI6MTc1MTU0NDU0NH0.ETM2hCU_5EtVcKAABd_69fyxS-FNuJ5Mv-QbY014sBY"; + "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2NCIsImFjdGlvbiI6ImF1dGgiLCJ0b2tlbi12ZXJzaW9uIjowLCJpYXQiOjE3NTE1Mzg0MDAsImV4cCI6MTc1MTYyNDgwMH0.QIGyV9_jbtv0F8YzbzIgn_669HJz2ftI8KckpPGN0UU"; let socket: Socket | null = null; diff --git a/store/telemetrySlice.ts b/store/telemetrySlice.ts index 77381a2..a1e873c 100644 --- a/store/telemetrySlice.ts +++ b/store/telemetrySlice.ts @@ -1,13 +1,24 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { BmsState } from "@/constants/types"; +import { createSlice, PayloadAction, createAsyncThunk } from "@reduxjs/toolkit"; interface TelemetryState { - SoH: number; - SoC: number; + SoH: number | null; + SoC: number | null; + chargingState: BmsState | null; + lat: number | null; + lon: number | null; + loading: boolean; + error: string | null; } const initialState: TelemetryState = { - SoH: 0, - SoC: 0, + SoH: null, + SoC: null, + chargingState: null, + lat: null, + lon: null, + loading: false, + error: null, }; export const telemetrySlice = createSlice({ @@ -17,8 +28,17 @@ export const telemetrySlice = createSlice({ updateTelemetry: (state, action: PayloadAction) => { return { ...state, ...action.payload }; }, + setTelemetryLoading: (state) => { + state.loading = true; + state.error = null; + }, + setTelemetryError: (state, action: PayloadAction) => { + state.loading = false; + state.error = action.payload; + }, }, }); -export const { updateTelemetry } = telemetrySlice.actions; +export const { updateTelemetry, setTelemetryLoading, setTelemetryError } = + telemetrySlice.actions; export default telemetrySlice.reducer; diff --git a/theme/dark.ts b/theme/dark.ts new file mode 100644 index 0000000..188099a --- /dev/null +++ b/theme/dark.ts @@ -0,0 +1,24 @@ +import { AppTheme } from "./types"; + +export const DarkTheme: AppTheme = { + dark: true, + colors: { + primary: "#008761", + background: "#252A34", + card: "#1C1E21", + text: "#F3F5F8", + border: "#4A4F57", + notification: "#F4C5C3", + + secondaryBackground: "#1C1E21", + accent: "#F3F5F8", + success: "#B8F1E3", + warning: "#FDE9E7", + info: "#D7E6FD", + muted: "#B9BDC5", + }, + fonts: { + regular: "System", + bold: "System", + }, +}; diff --git a/theme/index.ts b/theme/index.ts new file mode 100644 index 0000000..4362ddc --- /dev/null +++ b/theme/index.ts @@ -0,0 +1,3 @@ +export { LightTheme } from "./light"; +export { DarkTheme } from "./dark"; +export type { AppTheme } from "./types"; diff --git a/theme/light.ts b/theme/light.ts new file mode 100644 index 0000000..6a36318 --- /dev/null +++ b/theme/light.ts @@ -0,0 +1,25 @@ +import { AppTheme } from "./types"; + +export const LightTheme: AppTheme = { + dark: false, + colors: { + primary: "#008761", + background: "#F3F5F8", + card: "#FFFFFF", + text: "#1C1E21", + border: "#B9BDC5", + notification: "#F4C5C3", + + // custom additions + secondaryBackground: "#E5EBFD", + accent: "#252A34", + success: "#B8F1E3", + warning: "#FDE9E7", + info: "#D7E6FD", + muted: "#4A4F57", + }, + fonts: { + regular: "System", + bold: "System", + }, +}; diff --git a/theme/types.ts b/theme/types.ts new file mode 100644 index 0000000..01707a0 --- /dev/null +++ b/theme/types.ts @@ -0,0 +1,22 @@ +export interface AppTheme { + dark: boolean; + colors: { + primary: string; + background: string; + card: string; + text: string; + border: string; + notification: string; + + secondaryBackground?: string; + accent?: string; + success?: string; + warning?: string; + info?: string; + muted?: string; + }; + fonts: { + regular: string; + bold?: string; + }; +} diff --git a/theme/useThemeColors.ts b/theme/useThemeColors.ts new file mode 100644 index 0000000..1ddb18e --- /dev/null +++ b/theme/useThemeColors.ts @@ -0,0 +1,7 @@ +import { useTheme } from "@react-navigation/native"; +import { AppTheme } from "./types"; + +export function useThemeColors(): AppTheme["colors"] { + const { colors } = useTheme(); + return colors; +} diff --git a/utils/Map.ts b/utils/Map.ts new file mode 100644 index 0000000..a45fcea --- /dev/null +++ b/utils/Map.ts @@ -0,0 +1,37 @@ +export function calculateBearing( + lat1: number, + lon1: number, + lat2: number, + lon2: number +): number { + const φ1 = (lat1 * Math.PI) / 180; // Convert to radians + const φ2 = (lat2 * Math.PI) / 180; + const Δλ = ((lon2 - lon1) * Math.PI) / 180; + + // Bearing formula + const y = Math.sin(Δλ) * Math.cos(φ2); + const x = + Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ); + let θ = Math.atan2(y, x); + θ = (θ * 180) / Math.PI; // Convert back to degrees + return (θ + 360) % 360; // Normalize to 0-360° +} + +export function calculateDistance( + lat1: number, + lon1: number, + lat2: number, + lon2: number +): number { + const R = 6371e3; // Earth radius in meters + const φ1 = (lat1 * Math.PI) / 180; + const φ2 = (lat2 * Math.PI) / 180; + const Δφ = ((lat2 - lat1) * Math.PI) / 180; + const Δλ = ((lon2 - lon1) * Math.PI) / 180; + + const a = + Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return R * c; // Distance in meters +}