geofence impemented

This commit is contained in:
dimitar committed 2026-04-02 22:47:27 +02:00
1 parent e2706118d1
commit 71ccea85d2
16 files changed
+1006 -176

No files matched your search

+6 -2
View File
@@ -17,7 +17,8 @@
"infoPlist": {
"NSCameraUsageDescription": "This app uses the camera to scan food barcodes and identify nutritional information.",
"NSUserNotificationsUsageDescription": "This app uses notifications to keep you updated on your fitness progress, recommendation approvals, and important reminders.",
"NSMotionUsageDescription": "This app uses motion data to track your daily steps and activity progress."
"NSMotionUsageDescription": "This app uses motion data to track your daily steps and activity progress.",
"NSLocationWhenInUseUsageDescription": "This app uses your location to verify you are at your gym when checking in and checking out."
}
},
"android": {
@@ -29,7 +30,9 @@
"CAMERA",
"POST_NOTIFICATIONS",
"android.permission.CAMERA",
"android.permission.ACTIVITY_RECOGNITION"
"android.permission.ACTIVITY_RECOGNITION",
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.ACCESS_COARSE_LOCATION"
],
"package": "com.anonymous.fitai"
},
@@ -40,6 +43,7 @@
"expo-router",
"expo-font",
"expo-barcode-scanner",
"expo-location",
[
"expo-notifications",
{
+10
View File
@@ -29,6 +29,7 @@
"expo-haptics": "^15.0.7",
"expo-linear-gradient": "~15.0.7",
"expo-linking": "~8.0.0",
"expo-location": "~19.0.7",
"expo-notifications": "~0.32.0",
"expo-router": "~6.0.14",
"expo-secure-store": "~15.0.7",
@@ -7464,6 +7465,15 @@
"react-native": "*"
}
},
"node_modules/expo-location": {
"version": "19.0.8",
"resolved": "https://registry.npmjs.org/expo-location/-/expo-location-19.0.8.tgz",
"integrity": "sha512-H/FI75VuJ1coodJbbMu82pf+Zjess8X8Xkiv9Bv58ZgPKS/2ztjC1YO1/XMcGz7+s9DrbLuMIw22dFuP4HqneA==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-modules-autolinking": {
"version": "3.0.22",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.22.tgz",
+1
View File
@@ -35,6 +35,7 @@
"expo-haptics": "^15.0.7",
"expo-linear-gradient": "~15.0.7",
"expo-linking": "~8.0.0",
"expo-location": "~19.0.7",
"expo-notifications": "~0.32.0",
"expo-router": "~6.0.14",
"expo-secure-store": "~15.0.7",
+50 -37
View File
@@ -2,46 +2,59 @@ import { apiClient } from "./client";
import { API_ENDPOINTS } from "../config/api";
export interface Attendance {
id: string;
checkInTime: string;
checkOutTime?: string;
type: string;
notes?: string;
id: string;
checkInTime: string;
checkOutTime?: string;
type: string;
notes?: string;
}
export interface AttendanceLocationPayload {
latitude: number;
longitude: number;
accuracy: number;
}
export const attendanceApi = {
getHistory: async (token: string): Promise<Attendance[]> => {
try {
const response = await apiClient.get(API_ENDPOINTS.ATTENDANCE.HISTORY, {
headers: { Authorization: `Bearer ${token}` },
});
return response.data;
} catch (error) {
throw error;
}
},
getHistory: async (token: string): Promise<Attendance[]> => {
try {
const response = await apiClient.get(API_ENDPOINTS.ATTENDANCE.HISTORY, {
headers: { Authorization: `Bearer ${token}` },
});
return response.data;
} catch (error) {
throw error;
}
},
checkIn: async (type: string, token: string): Promise<void> => {
try {
await apiClient.post(
API_ENDPOINTS.ATTENDANCE.CHECK_IN,
{ type },
{ headers: { Authorization: `Bearer ${token}` } },
);
} catch (error) {
throw error;
}
},
checkIn: async (
type: string,
token: string,
location: AttendanceLocationPayload,
): Promise<void> => {
try {
await apiClient.post(
API_ENDPOINTS.ATTENDANCE.CHECK_IN,
{ type, location },
{ headers: { Authorization: `Bearer ${token}` } },
);
} catch (error) {
throw error;
}
},
checkOut: async (token: string): Promise<void> => {
try {
await apiClient.post(
API_ENDPOINTS.ATTENDANCE.CHECK_OUT,
{},
{ headers: { Authorization: `Bearer ${token}` } },
);
} catch (error) {
throw error;
}
},
checkOut: async (
token: string,
location: AttendanceLocationPayload,
): Promise<void> => {
try {
await apiClient.post(
API_ENDPOINTS.ATTENDANCE.CHECK_OUT,
{ location },
{ headers: { Authorization: `Bearer ${token}` } },
);
} catch (error) {
throw error;
}
},
};
+4
View File
@@ -6,6 +6,10 @@ export interface Gym {
id: string;
name: string;
location?: string;
latitude?: number | null;
longitude?: number | null;
geofenceRadiusMeters?: number;
geofenceEnabled?: boolean;
}
export const gymsApi = {
+21 -2
View File
@@ -10,6 +10,7 @@ import {
Alert,
AppState,
} from "react-native";
import * as Location from "expo-location";
import { useAuth, useUser } from "@clerk/clerk-expo";
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
import { useFocusEffect } from "@react-navigation/native";
@@ -236,11 +237,29 @@ export default function HomeScreen() {
return;
}
const permission = await Location.requestForegroundPermissionsAsync();
if (permission.status !== "granted") {
Alert.alert(
"Location required",
"Location access is required to check in and check out.",
);
return;
}
const position = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced,
});
const locationPayload = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy ?? 999,
};
if (activeWorkoutSession) {
await attendanceApi.checkOut(token);
await attendanceApi.checkOut(token, locationPayload);
Alert.alert("Workout logged", "Session ended successfully.");
} else {
await attendanceApi.checkIn("gym", token);
await attendanceApi.checkIn("gym", token, locationPayload);
Alert.alert("Workout started", "Session started successfully.");
}