Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
bf50e5b
fix: improve tracking type handling and fetch logic in exercise queries
isotronic Jun 3, 2026
21c3e5f
fix: update privacy settings handling and synchronize local state wit…
isotronic Jun 3, 2026
2a7bbe6
fix: refactor test helpers for useTrackedExercisesQuery and improve m…
isotronic Jun 3, 2026
fff653a
feat: add sort_order column to tracked_exercises with backfill migration
isotronic Jun 3, 2026
43d33b2
feat: assign sort_order when inserting a new tracked exercise
isotronic Jun 3, 2026
d1bf9c1
feat: add reorderTrackedExercises database function with tests
isotronic Jun 3, 2026
3f2a625
fix: guard empty array in reorderTrackedExercises and strengthen tests
isotronic Jun 3, 2026
91aa767
feat: add useReorderTrackedExercisesMutation hook
isotronic Jun 3, 2026
e6620fd
fix: add tests and explicit generics to useReorderTrackedExercisesMut…
isotronic Jun 3, 2026
21d862b
feat: order tracked exercises by sort_order in query
isotronic Jun 3, 2026
dd289b4
feat: add isReorderMode prop to ExerciseCompactCard with drag handle
isotronic Jun 3, 2026
2f5c45f
feat: add drag-and-drop reorder mode for tracked exercises on stats s…
isotronic Jun 3, 2026
69b84fe
fix: memoize drag handler, reset reorder mode on blur, move inline st…
isotronic Jun 3, 2026
88d8da9
fix: use exclusive transaction in migration, add error handling to re…
isotronic Jun 3, 2026
62485e2
feat: add new translation string
isotronic Jun 3, 2026
6ee7057
fix: correct query key in plan and workout publish mutations
isotronic Jun 3, 2026
5d0e5df
feat: add fetchAllPlanIds, fetchAllStandaloneWorkoutIds, fetchAllCust…
isotronic Jun 3, 2026
a69f5d5
test: add empty-array coverage for fetchAllStandaloneWorkoutIds and f…
isotronic Jun 3, 2026
65642cf
feat: add bulkPublishAllPlans, bulkPublishAllStandaloneWorkouts, bulk…
isotronic Jun 3, 2026
773d2ee
test: add empty-case coverage for bulkPublishAllStandaloneWorkouts an…
isotronic Jun 3, 2026
b0b7540
test: add partial-failure coverage for bulkPublishAllPlans, document …
isotronic Jun 3, 2026
59e93a4
feat: extract PrivacySettings as a self-contained component with bulk…
isotronic Jun 3, 2026
ac58bf4
refactor: replace inline privacy section in settings with PrivacySett…
isotronic Jun 3, 2026
c3954fc
style: fix prettier formatting in PrivacySettings
isotronic Jun 3, 2026
334c90a
feat: show publish toggle for plans and workouts regardless of global…
isotronic Jun 3, 2026
a4d9d11
feat: auto-publish new plans and standalone workouts when global shar…
isotronic Jun 3, 2026
fcadd6f
feat: add useSocialSyncOnStartup to publish any missing shared items …
isotronic Jun 3, 2026
694c774
docs: clarify hasSynced guard in useSocialSyncOnStartup
isotronic Jun 3, 2026
5c8da4d
feat: add privacy settings shortcut to friends screen header
isotronic Jun 3, 2026
936f870
style: add horizontal padding to privacy modal ScrollView
isotronic Jun 3, 2026
cd5d53f
feat: show pending friend requests badge on More tab
isotronic Jun 3, 2026
86ea2cc
fix: show friendship start date as absolute date instead of relative …
isotronic Jun 3, 2026
35f5b82
fix: use device locale for friendship start date
isotronic Jun 3, 2026
7bb5d80
feat: add 5 new translations
isotronic Jun 3, 2026
430a673
fix: update read permissions for shared content to include owner access
isotronic Jun 3, 2026
057e60c
fix: exclude premade plans from fetching and sharing operations
isotronic Jun 3, 2026
58ceecc
fix: match cloud icon color for all plan items
isotronic Jun 4, 2026
241921e
feat: add published status to standalone workout items
isotronic Jun 4, 2026
fd2dac2
feat: extract RestTimerOverlay to avoid rest timer duplicate code
isotronic Jun 4, 2026
491b6f2
feat: add useWorkoutImmersiveMode hook and integrate into WorkoutOver…
isotronic Jun 4, 2026
db9cf72
feat: change disabled complete set button to update button when a set…
isotronic Jun 4, 2026
78de147
fix: update default settings for unilateral counting and paired weigh…
isotronic Jun 4, 2026
41768ed
feat: add 9 new translations
isotronic Jun 4, 2026
d4bea14
feat: enhance progression logic for moderate effort ratings and add f…
isotronic Jun 4, 2026
6120e03
feat: add confirmation alerts for saving workouts to plan and standal…
isotronic Jun 4, 2026
5485f74
fix: review comments
isotronic Jun 8, 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
6 changes: 6 additions & 0 deletions app/(app)/(tabs)/(plans)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { t } from "@lingui/core/macro";
import { useAppTheme } from "@/theme";
import type { AppThemeColors } from "@/theme/types";
import { usePublishedPlanIdsQuery } from "@/hooks/usePublishedPlanIdsQuery";
import { usePublishedWorkoutIdsQuery } from "@/hooks/usePublishedWorkoutIdsQuery";
import { useSocialStore } from "@/store/socialStore";

export default function PlansScreen() {
Expand All @@ -38,6 +39,7 @@ export default function PlansScreen() {
error: standaloneError,
} = useStandaloneWorkoutsQuery();
const { data: publishedPlanIds } = usePublishedPlanIdsQuery();
const { data: publishedWorkoutIds } = usePublishedWorkoutIdsQuery();
const { privacySettings } = useSocialStore();

useEffect(() => {
Expand Down Expand Up @@ -159,6 +161,10 @@ export default function PlansScreen() {
workout={item}
onPress={() => handleViewWorkout(item)}
countUnilateralDouble={countUnilateralDouble}
isPublished={
!!privacySettings?.shareStandaloneWorkouts &&
!!publishedWorkoutIds?.includes(String(item.id))
}
Comment thread
isotronic marked this conversation as resolved.
/>
))
) : (
Expand Down
4 changes: 1 addition & 3 deletions app/(app)/(tabs)/(plans)/overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import { useDeloadWeekMutation } from "@/hooks/useDeloadWeekMutation";
import { useProgressionSettingsQuery } from "@/hooks/useProgressionSettingsQuery";
import { getCurrentISOWeek } from "@/utils/isoWeek";
import { AuthContext } from "@/context/AuthProvider";
import { useSocialStore } from "@/store/socialStore";
import { usePlanPublishQuery } from "@/hooks/usePlanPublishQuery";
import { usePlanPublishMutation } from "@/hooks/usePlanPublishMutation";

Expand Down Expand Up @@ -105,8 +104,7 @@ export default function PlanOverviewScreen() {
const deloadMutation = useDeloadWeekMutation(Number(planId));

const user = useContext(AuthContext);
const { privacySettings } = useSocialStore();
const showShareToggle = !!user && !!privacySettings?.sharePlans;
const showShareToggle = !!user && !plan?.app_plan_id;
const { data: isPublished = false, isLoading: isPublishLoading } =
usePlanPublishQuery(showShareToggle ? Number(planId) : null);
const publishMutation = usePlanPublishMutation(Number(planId));
Expand Down
4 changes: 1 addition & 3 deletions app/(app)/(tabs)/(plans)/standalone-workout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import { t } from "@lingui/core/macro";
import { useAppTheme, radii } from "@/theme";
import type { AppThemeColors } from "@/theme/types";
import { AuthContext } from "@/context/AuthProvider";
import { useSocialStore } from "@/store/socialStore";
import { useWorkoutPublishQuery } from "@/hooks/useWorkoutPublishQuery";
import { useWorkoutPublishMutation } from "@/hooks/useWorkoutPublishMutation";

Expand All @@ -53,8 +52,7 @@ export default function StandaloneWorkoutScreen() {
const distanceUnit = settings?.distanceUnit || "m";

const user = useContext(AuthContext);
const { privacySettings } = useSocialStore();
const showShareToggle = !!user && !!privacySettings?.shareStandaloneWorkouts;
const showShareToggle = !!user;
const { data: isPublished = false, isLoading: isPublishLoading } =
useWorkoutPublishQuery(showShareToggle ? workoutId : null);
const publishMutation = useWorkoutPublishMutation(workoutId);
Expand Down
36 changes: 17 additions & 19 deletions app/(app)/(tabs)/(stats)/exercises.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,26 +92,24 @@ export default function ExercisesScreen() {
(exerciseId) => !selectedExercises.includes(exerciseId),
);

// Insert new exercises into the tracked_exercises table
for (const exerciseId of newExercises) {
await db.runAsync(
`
INSERT INTO tracked_exercises (exercise_id)
VALUES (?)
`,
[exerciseId],
);
}
await db.withExclusiveTransactionAsync(async (txn) => {
// Insert new exercises into the tracked_exercises table
for (const exerciseId of newExercises) {
await txn.runAsync(
`INSERT INTO tracked_exercises (exercise_id, sort_order)
VALUES (?, (SELECT COALESCE(MAX(sort_order), 0) + 1 FROM tracked_exercises))`,
[exerciseId],
);
}

// Delete unselected exercises from the tracked_exercises table
for (const exerciseId of removedExercises) {
await db.runAsync(
`
DELETE FROM tracked_exercises WHERE exercise_id = ?
`,
[exerciseId],
);
}
// Delete unselected exercises from the tracked_exercises table
for (const exerciseId of removedExercises) {
await txn.runAsync(
`DELETE FROM tracked_exercises WHERE exercise_id = ?`,
[exerciseId],
);
}
});

queryclient.invalidateQueries({ queryKey: ["trackedExercises"] });
router.back();
Expand Down
105 changes: 83 additions & 22 deletions app/(app)/(tabs)/(stats)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ import { WorkoutCalendarModal } from "@/components/stats/WorkoutCalendarModal";
import { InsightsStrip } from "@/components/stats/InsightsStrip";
import { StatsTile } from "@/components/stats/StatsTile";
import { ExerciseCompactCard } from "@/components/stats/ExerciseCompactCard";
import { useRouter } from "expo-router";
import { useRouter, useFocusEffect } from "expo-router";
import { useSettingsQuery } from "@/hooks/useSettingsQuery";
import { useBodyMeasurementSessionsQuery } from "@/hooks/useBodyMeasurementSessionsQuery";
import { useTrackedExercisesQuery } from "@/hooks/useTrackedExercisesQuery";
import { useReorderTrackedExercisesMutation } from "@/hooks/useReorderTrackedExercisesMutation";
import { useStatsInsights } from "@/hooks/useStatsInsights";
import Sortable from "react-native-sortables";
import { WorkoutBarChart } from "@/components/charts/WorkoutBarChart";
import { VolumeBarChart } from "@/components/charts/VolumeBarChart";
import BodyPartChart from "@/components/charts/BodyPartChart";
Expand Down Expand Up @@ -114,6 +116,8 @@ export default function StatsScreen() {
);
const [historyVisible, setHistoryVisible] = useState(false);
const [selectedDate, setSelectedDate] = useState<string | null>(null);
const [isReorderMode, setIsReorderMode] = useState(false);
const reorderMutation = useReorderTrackedExercisesMutation();

useEffect(() => {
if (settings?.timeRange) setSelectedTimeRange(settings.timeRange);
Expand Down Expand Up @@ -317,6 +321,23 @@ export default function StatsScreen() {
});
}, [router, trackedExercises]);

const handleReorderDragEnd = useCallback(
({ fromIndex, toIndex }: { fromIndex: number; toIndex: number }) => {
if (fromIndex === toIndex) return;
const reordered = [...(trackedExercises ?? [])];
const [moved] = reordered.splice(fromIndex, 1);
reordered.splice(toIndex, 0, moved);
reorderMutation.mutate(reordered.map((e) => e.exercise_id));
},
[trackedExercises, reorderMutation],
);

useFocusEffect(
useCallback(() => {
return () => setIsReorderMode(false);
}, []),
);

const isLoading =
isLoadingWorkouts ||
isLoadingExercises ||
Expand Down Expand Up @@ -514,31 +535,67 @@ export default function StatsScreen() {
<ThemedText style={styles.sectionTitle}>
<Trans>Tracked Exercises</Trans>
</ThemedText>
<Button
mode="text"
compact
labelStyle={{ color: colors.accent, fontSize: 13 }}
onPress={handleManageExercisesPress}
>
{trackedExercises && trackedExercises.length > 0 ? (
<Trans>Manage</Trans>
) : (
<Trans>+ Add</Trans>
<View style={styles.exerciseHeaderButtons}>
{!isReorderMode && (
<Button
mode="text"
compact
labelStyle={{ color: colors.accent, fontSize: 13 }}
onPress={handleManageExercisesPress}
>
{trackedExercises && trackedExercises.length > 0 ? (
<Trans>Manage</Trans>
) : (
<Trans>+ Add</Trans>
)}
</Button>
)}
</Button>
{trackedExercises && trackedExercises.length > 1 && (
<Button
mode="text"
compact
labelStyle={{
color: isReorderMode ? colors.accent : colors.contentSecondary,
fontSize: 13,
}}
onPress={() => setIsReorderMode((v) => !v)}
>
{isReorderMode ? <Trans>Done</Trans> : <Trans>Reorder</Trans>}
</Button>
)}
</View>
</View>
{trackedExercises && trackedExercises.length > 0 ? (
trackedExercises.map((exercise) => (
<ExerciseCompactCard
key={exercise.exercise_id}
exercise={exercise}
weightUnit={weightUnit}
distanceUnit={distanceUnit}
onPress={() =>
handleExercisePress(exercise.exercise_id, exercise.name)
}
isReorderMode ? (
<Sortable.Grid
columns={1}
data={trackedExercises}
keyExtractor={(item) => item.exercise_id.toString()}
renderItem={({ item }) => (
<ExerciseCompactCard
exercise={item}
weightUnit={weightUnit}
distanceUnit={distanceUnit}
isReorderMode
onPress={() => {}}
/>
)}
onDragEnd={handleReorderDragEnd}
showDropIndicator
/>
))
) : (
trackedExercises.map((exercise) => (
<ExerciseCompactCard
key={exercise.exercise_id}
exercise={exercise}
weightUnit={weightUnit}
distanceUnit={distanceUnit}
onPress={() =>
handleExercisePress(exercise.exercise_id, exercise.name)
}
/>
))
)
) : (
<ThemedText style={{ color: colors.contentSecondary }}>
<Trans>No exercises tracked yet. Tap + Add to start.</Trans>
Expand Down Expand Up @@ -651,6 +708,10 @@ function createStyles(colors: AppThemeColors) {
flexWrap: "wrap",
gap: 8,
},
exerciseHeaderButtons: {
flexDirection: "row",
alignItems: "center",
},
measurementTile: {
padding: 12,
borderRadius: radii.md,
Expand Down
4 changes: 4 additions & 0 deletions app/(app)/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
import { AppMenu } from "@/components/AppMenu";
import { TabBarIcon } from "@/components/navigation/TabBarIcon";
import { useMenuStore } from "@/store/menuStore";
import { useSocialStore } from "@/store/socialStore";
import { t } from "@lingui/core/macro";
import { useAppTheme } from "@/theme";

export default function TabLayout() {
const { colors } = useAppTheme();
const insets = useSafeAreaInsets();
const openMenu = useMenuStore((s) => s.openMenu);
const { pendingRequests } = useSocialStore();

return (
<>
Expand Down Expand Up @@ -90,6 +92,8 @@ export default function TabLayout() {
}}
options={{
title: t`More`,
tabBarBadge:
pendingRequests.length > 0 ? pendingRequests.length : undefined,
tabBarIcon: ({ color, focused }) => (
<TabBarIcon
name={
Expand Down
Loading
Loading