add home workout quick action with attendance check-in/out
This commit is contained in:
parent
275248fc35
commit
2cff8eafbd
@ -10,7 +10,7 @@ import {
|
|||||||
Alert,
|
Alert,
|
||||||
AppState,
|
AppState,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useUser } from "@clerk/clerk-expo";
|
import { useAuth, useUser } from "@clerk/clerk-expo";
|
||||||
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
||||||
import { useFocusEffect } from "@react-navigation/native";
|
import { useFocusEffect } from "@react-navigation/native";
|
||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
@ -27,6 +27,7 @@ import { AddWaterModal } from "../../components/AddWaterModal";
|
|||||||
import { ScanFoodModal } from "../../components/ScanFoodModal";
|
import { ScanFoodModal } from "../../components/ScanFoodModal";
|
||||||
import { ActivityRing } from "../../components/ActivityRing";
|
import { ActivityRing } from "../../components/ActivityRing";
|
||||||
import { useMembership } from "../../hooks/useMembership";
|
import { useMembership } from "../../hooks/useMembership";
|
||||||
|
import { attendanceApi, type Attendance } from "../../api/attendance";
|
||||||
import {
|
import {
|
||||||
checkInsToActivities,
|
checkInsToActivities,
|
||||||
completedGoalsToActivities,
|
completedGoalsToActivities,
|
||||||
@ -74,6 +75,7 @@ const getRandomMotivation = () => {
|
|||||||
|
|
||||||
export default function HomeScreen() {
|
export default function HomeScreen() {
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
const { getToken } = useAuth();
|
||||||
const { colors, typography } = useTheme();
|
const { colors, typography } = useTheme();
|
||||||
const { features, membershipType } = useMembership();
|
const { features, membershipType } = useMembership();
|
||||||
const { refetchStatistics, forceRefresh, statistics, loading } =
|
const { refetchStatistics, forceRefresh, statistics, loading } =
|
||||||
@ -85,6 +87,9 @@ export default function HomeScreen() {
|
|||||||
const [scanFoodModalVisible, setScanFoodModalVisible] = useState(false);
|
const [scanFoodModalVisible, setScanFoodModalVisible] = useState(false);
|
||||||
const [calories, setCalories] = useState(0);
|
const [calories, setCalories] = useState(0);
|
||||||
const [waterIntake, setWaterIntake] = useState(0);
|
const [waterIntake, setWaterIntake] = useState(0);
|
||||||
|
const [activeWorkoutSession, setActiveWorkoutSession] =
|
||||||
|
useState<Attendance | null>(null);
|
||||||
|
const [workoutActionLoading, setWorkoutActionLoading] = useState(false);
|
||||||
const [motivationalMessage, setMotivationalMessage] = useState(
|
const [motivationalMessage, setMotivationalMessage] = useState(
|
||||||
"Let's crush it today! 💪",
|
"Let's crush it today! 💪",
|
||||||
);
|
);
|
||||||
@ -181,14 +186,78 @@ export default function HomeScreen() {
|
|||||||
}, getMillisecondsUntilNextMidnight() + 50);
|
}, getMillisecondsUntilNextMidnight() + 50);
|
||||||
}, [reconcileDailyMetrics]);
|
}, [reconcileDailyMetrics]);
|
||||||
|
|
||||||
|
const fetchActiveWorkoutSession = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const token = await getToken();
|
||||||
|
if (!token) {
|
||||||
|
setActiveWorkoutSession(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const history = await attendanceApi.getHistory(token);
|
||||||
|
if (history.length > 0 && !history[0].checkOutTime) {
|
||||||
|
setActiveWorkoutSession(history[0]);
|
||||||
|
} else {
|
||||||
|
setActiveWorkoutSession(null);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setActiveWorkoutSession(null);
|
||||||
|
}
|
||||||
|
}, [getToken]);
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
void reconcileDailyMetrics();
|
void reconcileDailyMetrics();
|
||||||
|
void fetchActiveWorkoutSession();
|
||||||
refetchStatistics();
|
refetchStatistics();
|
||||||
refetchGoals();
|
refetchGoals();
|
||||||
}, [reconcileDailyMetrics, refetchStatistics, refetchGoals]),
|
}, [
|
||||||
|
fetchActiveWorkoutSession,
|
||||||
|
reconcileDailyMetrics,
|
||||||
|
refetchStatistics,
|
||||||
|
refetchGoals,
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleWorkoutAction = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setWorkoutActionLoading(true);
|
||||||
|
const token = await getToken();
|
||||||
|
if (!token) {
|
||||||
|
Alert.alert("Sign in required", "Please sign in to log your workout.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeWorkoutSession) {
|
||||||
|
await attendanceApi.checkOut(token);
|
||||||
|
Alert.alert("Workout logged", "Session ended successfully.");
|
||||||
|
} else {
|
||||||
|
await attendanceApi.checkIn("gym", token);
|
||||||
|
Alert.alert("Workout started", "Session started successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
fetchActiveWorkoutSession(),
|
||||||
|
forceRefresh(),
|
||||||
|
refetchStatistics(),
|
||||||
|
]);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const message =
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Unable to update workout session. Please try again.";
|
||||||
|
Alert.alert("Workout action failed", message);
|
||||||
|
} finally {
|
||||||
|
setWorkoutActionLoading(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
activeWorkoutSession,
|
||||||
|
fetchActiveWorkoutSession,
|
||||||
|
forceRefresh,
|
||||||
|
getToken,
|
||||||
|
refetchStatistics,
|
||||||
|
]);
|
||||||
|
|
||||||
const onRefresh = useCallback(async () => {
|
const onRefresh = useCallback(async () => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
await Promise.all([forceRefresh(), refetchGoals()]);
|
await Promise.all([forceRefresh(), refetchGoals()]);
|
||||||
@ -499,11 +568,17 @@ export default function HomeScreen() {
|
|||||||
/>
|
/>
|
||||||
<View style={styles.quickActionsGrid}>
|
<View style={styles.quickActionsGrid}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => console.log("Log workout")}
|
onPress={handleWorkoutAction}
|
||||||
|
disabled={workoutActionLoading}
|
||||||
activeOpacity={0.85}
|
activeOpacity={0.85}
|
||||||
style={[
|
style={[
|
||||||
styles.quickActionCard,
|
styles.quickActionCard,
|
||||||
{ backgroundColor: colors.primary },
|
{
|
||||||
|
backgroundColor: activeWorkoutSession
|
||||||
|
? colors.warning
|
||||||
|
: colors.primary,
|
||||||
|
opacity: workoutActionLoading ? 0.7 : 1,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@ -512,12 +587,16 @@ export default function HomeScreen() {
|
|||||||
{ backgroundColor: "rgba(255,255,255,0.2)" },
|
{ backgroundColor: "rgba(255,255,255,0.2)" },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Ionicons name="barbell" size={28} color={colors.white} />
|
<Ionicons
|
||||||
|
name={activeWorkoutSession ? "stop-circle" : "barbell"}
|
||||||
|
size={28}
|
||||||
|
color={colors.white}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text
|
<Text
|
||||||
style={[typography.h4, { color: colors.white, marginTop: 12 }]}
|
style={[typography.h4, { color: colors.white, marginTop: 12 }]}
|
||||||
>
|
>
|
||||||
Workout
|
{activeWorkoutSession ? "End Workout" : "Start Workout"}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
@ -525,7 +604,16 @@ export default function HomeScreen() {
|
|||||||
{ color: "rgba(255,255,255,0.7)", marginTop: 4 },
|
{ color: "rgba(255,255,255,0.7)", marginTop: 4 },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Log your session
|
{activeWorkoutSession
|
||||||
|
? `In session since ${new Date(
|
||||||
|
activeWorkoutSession.checkInTime,
|
||||||
|
).toLocaleTimeString([], {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
})}`
|
||||||
|
: workoutActionLoading
|
||||||
|
? "Updating session..."
|
||||||
|
: "Log your session"}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user