diff --git a/src/components/SwipeableRow.tsx b/src/components/SwipeableRow.tsx
index 1c8bee9..7efa41b 100644
--- a/src/components/SwipeableRow.tsx
+++ b/src/components/SwipeableRow.tsx
@@ -19,7 +19,8 @@ export function SwipeableRow({ children, onDelete, deleteLabel = "Delete" }: Swi
const onHandlerStateChange = (event: any) => {
if (event.oldState === State.ACTIVE) {
const { translationX } = event.nativeEvent;
- const newOffset = lastOffset.current + translationX;
+ const clampedTranslation = Math.min(0, translationX);
+ const newOffset = lastOffset.current + clampedTranslation;
if (newOffset < -80) {
Animated.spring(translateX, {
diff --git a/src/features/active-workout/components/WorkoutSummary.tsx b/src/features/active-workout/components/WorkoutSummary.tsx
index abe7725..17e7dcf 100644
--- a/src/features/active-workout/components/WorkoutSummary.tsx
+++ b/src/features/active-workout/components/WorkoutSummary.tsx
@@ -1,14 +1,23 @@
-import { View, Text, ScrollView, Pressable, type ReactNode } from "react-native";
+import { View, Text, ScrollView, Pressable } from "react-native";
import { useTranslation } from "react-i18next";
import { formatTime, formatPace } from "@utils/formatTime";
import { formatDistance } from "@utils/formatDistance";
import { STROKE_COLORS } from "@constants/strokes";
+function formatPoolPace(seconds: number, poolLength: number): string {
+ if (poolLength === 0) return "--:--";
+ const pace = seconds / poolLength;
+ const mins = Math.floor(pace / 60);
+ const secs = Math.floor(pace % 60);
+ return `${mins}:${String(secs).padStart(2, "0")}`;
+}
+
type Set = {
stroke: string;
distancePerRep: number;
repCount: number;
position: number;
+ poolPace?: number | null;
};
type Lap = {
@@ -25,14 +34,15 @@ type Props = {
date?: string;
totalDistance: number;
totalDuration: number;
+ totalLengths?: number | null;
calories?: number | null;
avgHeartRate?: number | null;
+ avgPace?: number | null;
poolLength: number;
sets: Set[];
laps: Lap[];
avgSwolf: number | null;
onDone?: () => void;
- rightAction?: ReactNode;
};
export function WorkoutSummary({
@@ -40,14 +50,15 @@ export function WorkoutSummary({
date,
totalDistance,
totalDuration,
+ totalLengths,
calories,
avgHeartRate,
+ avgPace,
poolLength,
sets,
laps,
avgSwolf,
onDone,
- rightAction,
}: Props) {
const { t } = useTranslation("active-workout");
const { t: tc } = useTranslation("common");
@@ -65,7 +76,6 @@ export function WorkoutSummary({
{date || tc("pool", { length: poolLength })}
- {rightAction}
{/* Stats grid */}
@@ -99,8 +109,25 @@ export function WorkoutSummary({
{t("avgHeartRate")}
)}
+ {totalLengths != null && totalLengths > 0 && (
+
+ {totalLengths}
+ {t("lengths")}
+
+ )}
+ {avgPace != null && avgPace > 0 && (
+
+
+
+ {formatPace(totalDistance, totalDuration)}
+
+ {t("avgPace")}
+
+
+ )}
+
{/* SWOLF */}
{avgSwolf != null && avgSwolf > 0 && (
@@ -129,9 +156,16 @@ export function WorkoutSummary({
{tc(`stroke.${set.stroke.toLowerCase()}`)}
-
- {set.repCount}脳{set.distancePerRep}m
-
+
+
+ {set.repCount}脳{set.distancePerRep}m
+
+ {set.poolPace != null && set.poolPace > 0 && (
+
+ {formatPoolPace(set.poolPace, poolLength)}/pool
+
+ )}
+
))}
>
diff --git a/src/features/active-workout/screens/PostSwimScreen.tsx b/src/features/active-workout/screens/PostSwimScreen.tsx
index 364abcd..3bd06c9 100644
--- a/src/features/active-workout/screens/PostSwimScreen.tsx
+++ b/src/features/active-workout/screens/PostSwimScreen.tsx
@@ -56,6 +56,24 @@ export function PostSwimScreen() {
? Math.round(laps.reduce((acc, l) => acc + (l.swolf ?? 0), 0) / laps.length)
: null;
+ const totalLengths =
+ workout.totalDistance && workout.poolLength
+ ? Math.round(workout.totalDistance / workout.poolLength)
+ : null;
+
+ const avgPace =
+ workout.totalDistance && totalDuration
+ ? workout.totalDistance / totalDuration
+ : null;
+
+ const setsWithPace = (workout.sets ?? []).map((set: any) => {
+ const setLaps = laps.filter((l: any) => l.setPosition === set.position);
+ const setDuration = setLaps.reduce((acc: number, l: any) => acc + l.duration, 0);
+ const setDistance = setLaps.reduce((acc: number, l: any) => acc + l.distance, 0);
+ const poolPace = setDistance > 0 ? setDuration / setDistance : null;
+ return { ...set, poolPace };
+ });
+
setData({
name: workout.name,
poolLength: workout.poolLength,
@@ -63,9 +81,11 @@ export function PostSwimScreen() {
totalDuration,
calories: workout.calories,
avgHeartRate: workout.avgHeartRate,
- sets: workout.sets ?? [],
+ sets: setsWithPace,
laps,
avgSwolf,
+ totalLengths,
+ avgPace,
});
};
@@ -82,8 +102,10 @@ export function PostSwimScreen() {
name={data.name}
totalDistance={data.totalDistance}
totalDuration={data.totalDuration}
+ totalLengths={data.totalLengths}
calories={data.calories}
avgHeartRate={data.avgHeartRate}
+ avgPace={data.avgPace}
poolLength={data.poolLength}
sets={data.sets}
laps={data.laps}
diff --git a/src/features/history/screens/SessionDetailScreen.tsx b/src/features/history/screens/SessionDetailScreen.tsx
index c525bec..389a643 100644
--- a/src/features/history/screens/SessionDetailScreen.tsx
+++ b/src/features/history/screens/SessionDetailScreen.tsx
@@ -60,6 +60,18 @@ export function SessionDetailScreen({ route }: HistoryStackProps<"SessionDetail"
]);
};
+ useEffect(() => {
+ if (workout) {
+ navigation.setOptions({
+ headerRight: () => (
+
+ 馃棏
+
+ ),
+ });
+ }
+ }, [workout]);
+
if (!workout) {
return (
@@ -75,23 +87,38 @@ export function SessionDetailScreen({ route }: HistoryStackProps<"SessionDetail"
)
: null;
+ const totalLengths =
+ workout.totalDistance && workout.poolLength
+ ? Math.round(workout.totalDistance / workout.poolLength)
+ : null;
+
+ const avgPace =
+ workout.totalDistance && workout.totalDuration
+ ? workout.totalDistance / workout.totalDuration
+ : null;
+
+ const setsWithPace = workout.sets.map((set) => {
+ const setLaps = workout.laps.filter((l) => l.setPosition === set.position);
+ const setDuration = setLaps.reduce((acc, l) => acc + l.duration, 0);
+ const setDistance = setLaps.reduce((acc, l) => acc + l.distance, 0);
+ const poolPace = setDistance > 0 ? setDuration / setDistance : null;
+ return { ...set, poolPace };
+ });
+
return (
- 馃棏
-
- }
/>
);
}
diff --git a/src/i18n/de/active-workout.json b/src/i18n/de/active-workout.json
index e59abc1..16b2f64 100644
--- a/src/i18n/de/active-workout.json
+++ b/src/i18n/de/active-workout.json
@@ -17,6 +17,8 @@
"avgHeartRate": "Durchschn. Herzfrequenz",
"calories": "Kalorien",
"pace": "Tempo",
+ "lengths": "Bahnen",
+ "avgPace": "脴 Tempo",
"setRep": "Set {{set}} 路 Wiederholung {{rep}}",
"watchStatus": {
"ready": "Watch bereit",
diff --git a/src/i18n/dk/active-workout.json b/src/i18n/dk/active-workout.json
index b3342fd..27c1504 100644
--- a/src/i18n/dk/active-workout.json
+++ b/src/i18n/dk/active-workout.json
@@ -17,6 +17,8 @@
"avgHeartRate": "Gns. puls",
"calories": "Kalorier",
"pace": "Tempo",
+ "lengths": "Baner",
+ "avgPace": "Snit-tempo",
"setRep": "S忙t {{set}} 路 Gentagelse {{rep}}",
"watchStatus": {
"ready": "Watch klar",
diff --git a/src/i18n/en/active-workout.json b/src/i18n/en/active-workout.json
index 824085a..5343fcc 100644
--- a/src/i18n/en/active-workout.json
+++ b/src/i18n/en/active-workout.json
@@ -17,6 +17,8 @@
"avgHeartRate": "Avg Heart Rate",
"calories": "Calories",
"pace": "Pace",
+ "lengths": "Lengths",
+ "avgPace": "Avg pace",
"setRep": "Set {{set}} 路 Rep {{rep}}",
"watchStatus": {
"ready": "Watch ready",
diff --git a/src/i18n/es/active-workout.json b/src/i18n/es/active-workout.json
index 279ebc5..a520b8c 100644
--- a/src/i18n/es/active-workout.json
+++ b/src/i18n/es/active-workout.json
@@ -17,6 +17,8 @@
"avgHeartRate": "Freq. card. media",
"calories": "Calor铆as",
"pace": "Ritmo",
+ "lengths": "Tiradas",
+ "avgPace": "Ritmo medio",
"setRep": "Serie {{set}} 路 Repetici贸n {{rep}}",
"watchStatus": {
"ready": "Watch lista",
diff --git a/src/i18n/fr/active-workout.json b/src/i18n/fr/active-workout.json
index 4b2539e..ecdbbbc 100644
--- a/src/i18n/fr/active-workout.json
+++ b/src/i18n/fr/active-workout.json
@@ -17,6 +17,8 @@
"avgHeartRate": "Fr茅q. cardiaque moy.",
"calories": "Calories",
"pace": "Rythme",
+ "lengths": "Longueurs",
+ "avgPace": "Rythme moy.",
"setRep": "S茅rie {{set}} 路 R茅p茅tition {{rep}}",
"watchStatus": {
"ready": "Watch pr锚te",
diff --git a/src/i18n/it/active-workout.json b/src/i18n/it/active-workout.json
index 742f721..25f6ec4 100644
--- a/src/i18n/it/active-workout.json
+++ b/src/i18n/it/active-workout.json
@@ -17,6 +17,8 @@
"avgHeartRate": "Freq. card. media",
"calories": "Calorie",
"pace": "Ritmo",
+ "lengths": "Vasche",
+ "avgPace": "Passo medio",
"setRep": "Serie {{set}} 路 Ripetizione {{rep}}",
"watchStatus": {
"ready": "Watch pronta",
diff --git a/src/i18n/nl/active-workout.json b/src/i18n/nl/active-workout.json
index c9f985e..ad11d54 100644
--- a/src/i18n/nl/active-workout.json
+++ b/src/i18n/nl/active-workout.json
@@ -17,6 +17,8 @@
"avgHeartRate": "Gem. hartslag",
"calories": "Calorie毛n",
"pace": "Tempo",
+ "lengths": "Banen",
+ "avgPace": "Gem. tempo",
"setRep": "Set {{set}} 路 Herhaling {{rep}}",
"watchStatus": {
"ready": "Watch gereed",
diff --git a/src/i18n/pl/active-workout.json b/src/i18n/pl/active-workout.json
index dc04fdc..6e28584 100644
--- a/src/i18n/pl/active-workout.json
+++ b/src/i18n/pl/active-workout.json
@@ -17,6 +17,8 @@
"avgHeartRate": "艢r. t臋tno",
"calories": "Kalorie",
"pace": "Tempo",
+ "lengths": "D艂ugo艣ci",
+ "avgPace": "艢r. tempo",
"setRep": "Serie {{set}} 路 Powt贸rzenie {{rep}}",
"watchStatus": {
"ready": "Watch gotowa",
diff --git a/src/i18n/pt/active-workout.json b/src/i18n/pt/active-workout.json
index 1ad9af1..68e80f2 100644
--- a/src/i18n/pt/active-workout.json
+++ b/src/i18n/pt/active-workout.json
@@ -17,6 +17,8 @@
"avgHeartRate": "Freq. card. m茅dia",
"calories": "Calorias",
"pace": "Ritmo",
+ "lengths": "Comprimentos",
+ "avgPace": "Ritmo m茅dio",
"setRep": "S茅rie {{set}} 路 Repeti莽茫o {{rep}}",
"watchStatus": {
"ready": "Watch pronta",