Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
87cf904
fix: update SparklineChart height and adjust opacity for better visib…
isotronic May 17, 2026
1084136
fix: enhance SparklineChart with SVG rendering and improve performance
isotronic May 17, 2026
dbcefc1
fix: set staleTime and gcTime to 0 for workout queries to improve dat…
isotronic May 17, 2026
77c7b92
fix: format useCompletedWorkoutsQuery parameters for improved readabi…
isotronic May 17, 2026
7e471e2
fix: add preRangeBaseline to ExerciseDetail and update related querie…
isotronic May 17, 2026
3bbd8bb
fix: reorder time range options to improve user experience
isotronic May 17, 2026
49c387f
fix: enhance time range handling in charts with adaptive bucket sizes
isotronic May 17, 2026
c956c95
fix: add space to the right of the chart
isotronic May 17, 2026
c013653
feat: add excludeWarmupSets functionality to improve workout statisti…
isotronic May 17, 2026
1bea31a
fix: include excludeWarmup parameter in computeBestAchievement for ac…
isotronic May 17, 2026
82d4163
fix: update time range handling in groupSetsByTime to use latest comp…
isotronic May 17, 2026
20c823c
fix: adjust horizontal padding in SparklineChart for improved layout
isotronic May 17, 2026
0695987
fix: update time range handling in groupWorkoutsByTime and groupVolum…
isotronic May 17, 2026
cb4b522
fix: include is_warmup in fetchExerciseHistory for improved data accu…
isotronic May 17, 2026
1d00371
fix: implement warmup backfill for completed_sets with settings flag …
isotronic May 17, 2026
d7c5b71
feat: add Stats section header and divider in SettingsScreen
isotronic May 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/(app)/(tabs)/(stats)/exercise-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,22 @@ export default function ExerciseDetailScreen() {
const { data: settings } = useSettingsQuery();
const weightUnit = settings?.weightUnit || "kg";
const distanceUnit = settings?.distanceUnit || "m";
const excludeWarmup = settings?.excludeWarmupSets === "true";
const [timeRange, setTimeRange] = useState("30");

useEffect(() => {
if (name) navigation.setOptions({ title: name });
}, [name, navigation]);

useEffect(() => {
if (settings?.timeRange) setTimeRange(settings.timeRange);
}, [settings?.timeRange]);

const { data, isLoading } = useExerciseDetailQuery(
parseInt(exerciseId ?? "0"),
timeRange,
weightUnit,
excludeWarmup,
);

const convFactor = weightUnit === "lbs" ? 2.2046226 : 1;
Expand Down Expand Up @@ -117,6 +123,7 @@ export default function ExerciseDetailScreen() {
weightUnit={weightUnit}
distanceUnit={distanceUnit}
prValue={prValue > 0 ? prValue : undefined}
preRangeBaseline={data.preRangeBaseline}
/>
</View>
)}
Expand Down Expand Up @@ -145,6 +152,7 @@ export default function ExerciseDetailScreen() {
{new Date(set.date_completed).toLocaleDateString(undefined, {
day: "numeric",
month: "short",
year: "numeric",
})}
</ThemedText>
</View>
Expand Down
6 changes: 3 additions & 3 deletions app/(app)/(tabs)/(stats)/history-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default function HistoryDetailsScreen() {
const weightUnit = settings?.weightUnit || "kg";
const distanceUnit = settings?.distanceUnit || "m";
const bodyWeight = parseFloat(settings?.bodyWeight || "70");
const excludeWarmup = settings?.excludeWarmupSets === "true";

const deleteMutation = useDeleteCompletedWorkoutMutation();

Expand Down Expand Up @@ -93,18 +94,17 @@ export default function HistoryDetailsScreen() {

return workout.exercises.reduce((exerciseAcc, exercise) => {
const exerciseVolume = exercise.sets.reduce((setAcc, set) => {
// Check if the tracking type is "assistance" and apply custom calculation
if (excludeWarmup && set.is_warmup) return setAcc;
const weight =
exercise.exercise_tracking_type === "assisted"
? bodyWeight - (set.weight || 0)
: set.weight || 0;

return setAcc + weight * (set.reps || 0);
}, 0);

return parseFloat((exerciseAcc + exerciseVolume).toFixed(1));
}, 0);
}, [workout, bodyWeight]);
}, [workout, bodyWeight, excludeWarmup]);

if (isWorkoutLoading || !workout || settingsLoading) {
return (
Expand Down
43 changes: 34 additions & 9 deletions app/(app)/(tabs)/(stats)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,21 @@ import { useQueryClient } from "@tanstack/react-query";
import { formatToHoursMinutes } from "@/utils/utility";
import Bugsnag from "@bugsnag/expo";

const computeStats = (workouts: CompletedWorkout[], weightUnit: string) => {
const computeStats = (
workouts: CompletedWorkout[],
weightUnit: string,
excludeWarmup: boolean,
) => {
const tonDivisor = weightUnit === "lbs" ? 2000 : 1000;
const totalWorkouts = workouts.length;
const totalSets = workouts.reduce(
(acc, w) => acc + w.exercises.reduce((a, e) => a + e.sets.length, 0),
(acc, w) =>
acc +
w.exercises.reduce(
(a, e) =>
a + e.sets.filter((s) => !excludeWarmup || !s.is_warmup).length,
0,
),
0,
);
const totalVolume = workouts.reduce(
Expand All @@ -42,7 +52,9 @@ const computeStats = (workouts: CompletedWorkout[], weightUnit: string) => {
a +
e.sets.reduce(
(s, set) =>
s + (set.weight && set.reps ? set.weight * set.reps : 0),
(!excludeWarmup || !set.is_warmup) && set.weight && set.reps
? s + set.weight * set.reps
: s,
0,
),
0,
Expand All @@ -66,6 +78,7 @@ export default function StatsScreen() {
const { data: settings } = useSettingsQuery();
const weightUnit = settings?.weightUnit || "kg";
const distanceUnit = settings?.distanceUnit || "m";
const excludeWarmup = settings?.excludeWarmupSets === "true";
const [selectedTimeRange, setSelectedTimeRange] = useState<string>(
settings?.timeRange || "30",
);
Expand All @@ -83,12 +96,16 @@ export default function StatsScreen() {
data: trackedExercises,
isLoading: isLoadingTracked,
error: trackedError,
} = useTrackedExercisesQuery(selectedTimeRange);
} = useTrackedExercisesQuery(selectedTimeRange, excludeWarmup);
const {
data: completedWorkouts,
isLoading: isLoadingWorkouts,
error,
} = useCompletedWorkoutsQuery(weightUnit, distanceUnit, parseInt(selectedTimeRange));
} = useCompletedWorkoutsQuery(
weightUnit,
distanceUnit,
parseInt(selectedTimeRange),
);

useEffect(() => {
const anyError = error || exercisesError || trackedError;
Expand All @@ -111,6 +128,7 @@ export default function StatsScreen() {
parseInt(selectedTimeRange),
weightUnit,
distanceUnit,
excludeWarmup,
);

const handleTimeRangeChange = useCallback(
Expand All @@ -121,8 +139,6 @@ export default function StatsScreen() {
} catch (err: any) {
Bugsnag.notify(err);
} finally {
queryClient.invalidateQueries({ queryKey: ["completedWorkouts"] });
queryClient.invalidateQueries({ queryKey: ["trackedExercises"] });
queryClient.invalidateQueries({ queryKey: ["settings"] });
}
},
Expand Down Expand Up @@ -174,8 +190,14 @@ export default function StatsScreen() {
);
}

const current = computeStats(completedWorkouts ?? [], weightUnit);
const prev = prevWorkouts ? computeStats(prevWorkouts, weightUnit) : null;
const current = computeStats(
completedWorkouts ?? [],
weightUnit,
excludeWarmup,
);
const prev = prevWorkouts
? computeStats(prevWorkouts, weightUnit, excludeWarmup)
: null;

const volumeUnit = weightUnit === "lbs" ? "tn" : "t";
const formattedVolume = current.totalVolumeTons.toLocaleString(undefined, {
Expand Down Expand Up @@ -253,6 +275,7 @@ export default function StatsScreen() {
<WorkoutHistorySection
completedWorkouts={completedWorkouts ?? []}
onWorkoutPress={handleWorkoutPress}
excludeWarmup={excludeWarmup}
/>
</View>

Expand All @@ -279,6 +302,7 @@ export default function StatsScreen() {
completedWorkouts={completedWorkouts!}
timeRange={selectedTimeRange}
weightUnit={weightUnit}
excludeWarmup={excludeWarmup}
/>
</View>
)}
Expand All @@ -291,6 +315,7 @@ export default function StatsScreen() {
<BodyPartChart
completedWorkouts={completedWorkouts}
exercises={exercises?.otherExercises}
excludeWarmup={excludeWarmup}
/>
</View>

Expand Down
1 change: 1 addition & 0 deletions app/(app)/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ export default function HomeScreen() {
weeklyGoal={Number(settings?.weeklyGoal)}
weightUnit={settings?.weightUnit ?? "kg"}
streak={streak}
excludeWarmup={settings?.excludeWarmupSets === "true"}
/>
</View>
)}
Expand Down
34 changes: 34 additions & 0 deletions app/(app)/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export default function SettingsScreen() {
restTimerSound: settings?.restTimerSound,
restTimerNotification: settings?.restTimerNotification,
showOnboarding: settings?.showOnboarding,
excludeWarmupSets: settings?.excludeWarmupSets,
});

const [workoutReminderEnabled, setWorkoutReminderEnabled] = useState(false);
Expand Down Expand Up @@ -132,6 +133,7 @@ export default function SettingsScreen() {
restTimerSound: settings?.restTimerSound,
restTimerNotification: settings?.restTimerNotification,
showOnboarding: settings?.showOnboarding,
excludeWarmupSets: settings?.excludeWarmupSets,
});
setWorkoutReminderEnabled(settings.workoutReminderEnabled === "true");
try {
Expand Down Expand Up @@ -253,6 +255,11 @@ export default function SettingsScreen() {
updateSetting({ key: "keepScreenOn", value: value.toString() });
};

const toggleExcludeWarmupSets = (value: boolean) => {
setToggleValues({ ...toggleValues, excludeWarmupSets: value.toString() });
updateSetting({ key: "excludeWarmupSets", value: value.toString() });
};

const toggleVibration = (value: boolean) => {
setToggleValues({ ...toggleValues, restTimerVibration: value.toString() });
updateSetting({ key: "restTimerVibration", value: value.toString() });
Expand Down Expand Up @@ -791,6 +798,33 @@ export default function SettingsScreen() {
</View>
<Divider style={styles.divider} />

<View style={styles.section}>
<ThemedText style={styles.sectionHeader}>Stats</ThemedText>
<View style={styles.item}>
<MaterialCommunityIcons
name="fire-off"
size={24}
color={Colors.dark.icon}
style={styles.icon}
/>
<View style={styles.textContainer}>
<ThemedText style={styles.itemText}>
Exclude warmup sets from stats
</ThemedText>
<ThemedText style={styles.currentSetting}>
{toggleValues.excludeWarmupSets === "true" ? "Enabled" : "Disabled"}
</ThemedText>
</View>
<Switch
value={toggleValues.excludeWarmupSets === "true"}
onValueChange={toggleExcludeWarmupSets}
color={Colors.dark.tint}
style={styles.switch}
/>
</View>
</View>
<Divider style={styles.divider} />

<View style={styles.section}>
<ThemedText style={styles.sectionHeader}>Reminders</ThemedText>
<View style={styles.item}>
Expand Down
1 change: 1 addition & 0 deletions app/(app)/(workout)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ export default function WorkoutOverviewScreen() {
reps: set.reps ? parseInt(set.reps) : null,
time: set.time ? parseInt(set.time) : null,
distance: (set.distance !== "" && set.distance != null) ? parseFloat(set.distance) : null,
is_warmup: exercise.sets[parseInt(setIndex)]?.isWarmup || false,
}));

return { exercise_id: exercise.exercise_id, sets };
Expand Down
22 changes: 14 additions & 8 deletions app/(app)/(workout)/workout-summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,12 @@ function formatDuration(seconds: number): string {
return `${s}s`;
}

function computeVolume(workout: CompletedWorkout): number {
function computeVolume(workout: CompletedWorkout, excludeWarmup: boolean = false): number {
return workout.exercises.reduce(
(total, exercise) =>
total +
exercise.sets.reduce((setTotal, set) => {
if (set.weight != null && set.reps != null) {
if ((!excludeWarmup || !set.is_warmup) && set.weight != null && set.reps != null) {
return setTotal + set.weight * set.reps;
}
return setTotal;
Expand Down Expand Up @@ -387,6 +387,7 @@ export default function WorkoutSummaryScreen() {
const { data: settings } = useSettingsQuery();
const weightUnit = settings?.weightUnit ?? "kg";
const distanceUnit = settings?.distanceUnit ?? "m";
const excludeWarmup = settings?.excludeWarmupSets === "true";

const id = Number(completedWorkoutId);
const isValidId = Number.isFinite(id) && id > 0;
Expand Down Expand Up @@ -427,14 +428,19 @@ export default function WorkoutSummaryScreen() {
}, [history, workout]);

const currentVolume = useMemo(
() => (workout ? computeVolume(workout) : 0),
[workout],
() => (workout ? computeVolume(workout, excludeWarmup) : 0),
[workout, excludeWarmup],
);
const prevVolume = useMemo(
() => (prevWorkout ? computeVolume(prevWorkout) : 0),
[prevWorkout],
() => (prevWorkout ? computeVolume(prevWorkout, excludeWarmup) : 0),
[prevWorkout, excludeWarmup],
);

const countSets = (w: CompletedWorkout) =>
excludeWarmup
? w.exercises.reduce((t, e) => t + e.sets.filter((s) => !s.is_warmup).length, 0)
: w.total_sets_completed;

if (isLoading) {
return (
<View
Expand Down Expand Up @@ -472,7 +478,7 @@ export default function WorkoutSummaryScreen() {
? Math.round((workout.duration - prevWorkout.duration) / 60)
: 0;
const setsDiff = prevWorkout
? workout.total_sets_completed - prevWorkout.total_sets_completed
? countSets(workout) - countSets(prevWorkout)
: 0;
const volumeDiff = currentVolume - prevVolume;

Expand Down Expand Up @@ -509,7 +515,7 @@ export default function WorkoutSummaryScreen() {
<View style={styles.statsDivider} />
<StatChip
label="Sets"
value={String(workout.total_sets_completed)}
value={String(countSets(workout))}
icon="dumbbell"
/>
<View style={styles.statsDivider} />
Expand Down
12 changes: 9 additions & 3 deletions components/WeeklySummaryCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface Props {
weeklyGoal: number;
weightUnit: string;
streak: number;
excludeWarmup?: boolean;
}

function formatDuration(seconds: number): string {
Expand Down Expand Up @@ -47,6 +48,7 @@ function computeBestAchievement(
workoutsThisWeek: CompletedWorkout[],
allCompletedWorkouts: CompletedWorkout[],
weightUnit: string,
excludeWarmup: boolean = false,
): BestAchievement | null {
const today = new Date();
const currentWeekStart = startOfWeek(today, { weekStartsOn: 1 });
Expand All @@ -63,6 +65,7 @@ function computeBestAchievement(
for (const workout of lastWeekWorkouts) {
for (const ex of workout.exercises) {
for (const set of ex.sets) {
if (excludeWarmup && set.is_warmup) continue;
const metric = getProgressionMetric(
set.weight,
set.reps,
Expand All @@ -84,6 +87,7 @@ function computeBestAchievement(
for (const workout of workoutsThisWeek) {
for (const ex of workout.exercises) {
for (const set of ex.sets) {
if (excludeWarmup && set.is_warmup) continue;
const metric = getProgressionMetric(
set.weight,
set.reps,
Expand Down Expand Up @@ -158,6 +162,7 @@ export default function WeeklySummaryCard({
weeklyGoal,
weightUnit,
streak,
excludeWarmup = false,
}: Props) {
const totalDuration = useMemo(
() => workoutsThisWeek.reduce((sum, w) => sum + (w.duration ?? 0), 0),
Expand All @@ -169,23 +174,24 @@ export default function WeeklySummaryCard({
for (const workout of workoutsThisWeek) {
for (const ex of workout.exercises) {
for (const set of ex.sets) {
if (set.weight != null && set.reps != null) {
if ((!excludeWarmup || !set.is_warmup) && set.weight != null && set.reps != null) {
vol += set.weight * set.reps;
}
}
}
}
return vol;
}, [workoutsThisWeek]);
}, [workoutsThisWeek, excludeWarmup]);

const bestAchievement = useMemo(
() =>
computeBestAchievement(
workoutsThisWeek,
allCompletedWorkouts,
weightUnit,
excludeWarmup,
),
[workoutsThisWeek, allCompletedWorkouts, weightUnit],
[workoutsThisWeek, allCompletedWorkouts, weightUnit, excludeWarmup],
);

const volumeLabel = `${Math.round(totalVolume).toLocaleString()} ${weightUnit}`;
Expand Down
Loading
Loading