Skip to content

Commit 17c354c

Browse files
committed
fix:final-unit-sweep-hardcoded-kg
1 parent 33f6617 commit 17c354c

4 files changed

Lines changed: 288 additions & 91 deletions

File tree

src/screens/ActiveWorkoutScreen.js

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
matchesExerciseFilter,
4848
} from '../utils/exerciseFilters';
4949
import { resolveExerciseProfile } from '../domain/intelligence/exerciseProfileEngine';
50+
import { formatVolumeFromKg, formatWeightFromKg } from '../utils/weightUnits';
5051

5152
function genId() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 6); }
5253

@@ -66,7 +67,7 @@ function getExerciseMuscles(exercise) {
6667
return getExerciseFilterSummary(exercise, 6);
6768
}
6869

69-
function getPlateText(targetKg, barWeight, profilePlates) {
70+
function getPlateText(targetKg, barWeight, profilePlates, weightUnit = 'kg') {
7071
const perSide = (targetKg - barWeight) / 2;
7172
if (perSide <= 0) return 'Bar only';
7273
const plates = profilePlates?.length
@@ -78,7 +79,7 @@ function getPlateText(targetKg, barWeight, profilePlates) {
7879
while (rem >= p - 0.001) { result.push(p); rem = Math.round((rem - p) * 100) / 100; }
7980
}
8081
if (result.length === 0) return 'Bar only';
81-
return result.map(p => p + 'kg').join(' + ') + ' each side';
82+
return result.map(p => formatWeightFromKg(p, weightUnit)).join(' + ') + ' each side';
8283
}
8384

8485
function parseRepTarget(value, fallback = 8) {
@@ -191,29 +192,29 @@ const rt = StyleSheet.create({
191192

192193
// ─── PLATE MODAL ─────────────────────────────────────────────────────────────
193194

194-
function PlateModal({ visible, targetKg, barWeight, onClose }) {
195+
function PlateModal({ visible, targetKg, barWeight, onClose, weightUnit = 'kg' }) {
195196
const result = calcPlates(targetKg, barWeight);
196197
return (
197198
<Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
198199
<View style={pm.overlay}>
199200
<View style={pm.sheet}>
200201
<Text style={pm.title}>PLATE CALCULATOR</Text>
201-
<Text style={pm.target}>{targetKg} kg total</Text>
202+
<Text style={pm.target}>{formatWeightFromKg(targetKg, weightUnit)} total</Text>
202203
{result.valid ? (
203204
<>
204205
<Text style={pm.label}>Each side:</Text>
205206
{result.each.length === 0
206-
? <Text style={pm.noPlates}>Bar only ({barWeight}kg)</Text>
207+
? <Text style={pm.noPlates}>Bar only ({formatWeightFromKg(barWeight, weightUnit)})</Text>
207208
: result.each.map((p, i) => (
208209
<View key={i} style={pm.plateRow}>
209210
<View style={[pm.plateVis, { width: 20 + p.kg * 2 }]} />
210-
<Text style={pm.plateText}>{p.kg}kg x {p.count}</Text>
211+
<Text style={pm.plateText}>{formatWeightFromKg(p.kg, weightUnit)} x {p.count}</Text>
211212
</View>
212213
))}
213-
<Text style={pm.sub}>Bar: {barWeight}kg · Total: {result.total}kg</Text>
214+
<Text style={pm.sub}>Bar: {formatWeightFromKg(barWeight, weightUnit)} · Total: {formatWeightFromKg(result.total, weightUnit)}</Text>
214215
</>
215216
) : (
216-
<Text style={{ color: '#FF4500', textAlign: 'center' }}>Cannot load {targetKg}kg with available plates.</Text>
217+
<Text style={{ color: '#FF4500', textAlign: 'center' }}>Cannot load {formatWeightFromKg(targetKg, weightUnit)} with available plates.</Text>
217218
)}
218219
<TouchableOpacity style={pm.close} onPress={() => { triggerHaptic('selection').catch(() => {}); onClose(); }}>
219220
<Text style={{ color: '#f0f0f0', fontWeight: '700', letterSpacing: 2 }}>CLOSE</Text>
@@ -842,7 +843,7 @@ function WorkoutContent({ plan, day, planIndex, dayIndex, navigation }) {
842843

843844
if (weight > 0 && (!pb[key] || weight > pb[key])) {
844845
updatePb(key, weight);
845-
dispatch({ type: 'SET_PB_NOTIF', message: 'NEW PB: ' + ex.name + ' — ' + weight + 'kg' });
846+
dispatch({ type: 'SET_PB_NOTIF', message: 'NEW PB: ' + ex.name + ' — ' + formatWeightFromKg(weight, settings?.weightUnit || 'kg') });
846847
setTimeout(() => dispatch({ type: 'SET_PB_NOTIF', message: null }), 3000);
847848
primarySetEvent = 'prUnlocked';
848849
}
@@ -1128,7 +1129,7 @@ function WorkoutContent({ plan, day, planIndex, dayIndex, navigation }) {
11281129
}).filter(Boolean);
11291130
if (targets.length > 0) setNextTargets(targets);
11301131
} catch (_) {}
1131-
const completionLine = summaryText || `You lifted ${Math.round(totalVolume).toLocaleString()} kg total.`;
1132+
const completionLine = summaryText || `You lifted ${formatVolumeFromKg(totalVolume, settings?.weightUnit || 'kg')} total.`;
11321133
const prLine = prEvents.length ? ` ${prEvents.length} PR event${prEvents.length > 1 ? 's' : ''}.` : '';
11331134
const milestoneLine = addResult?.unlockedMilestones?.length
11341135
? ` ${addResult.unlockedMilestones.length} milestone unlocked.`
@@ -1304,7 +1305,7 @@ function WorkoutContent({ plan, day, planIndex, dayIndex, navigation }) {
13041305
<Text style={s.exTarget}>{ex.primary || (ex.primaryMuscles || [])[0] || '—'} · {workingExercises[exIndex].sets}x{workingExercises[exIndex].reps}</Text>
13051306
{suggestion ? (
13061307
<Text style={[s.exTargetHint, { color: suggestion.action === 'reduce' ? '#FF8E8E' : colors.subtext }]}>
1307-
Next target {suggestion.targetWeight}kg x {suggestion.targetReps}
1308+
Next target {Number(suggestion.targetWeight || 0) > 0 ? formatWeightFromKg(suggestion.targetWeight, settings?.weightUnit || 'kg') : 'BW'} x {Math.max(1, Number(suggestion.targetReps || 0))}
13081309
{suggestion.plateauSignal ? ` · Plateau: ${suggestion.plateauSignal.recommendation}` : ''}
13091310
{suggestion.deloadSignal ? ` · ${suggestion.deloadSignal.recommendation}` : ''}
13101311
</Text>
@@ -1319,27 +1320,38 @@ function WorkoutContent({ plan, day, planIndex, dayIndex, navigation }) {
13191320
<TouchableOpacity style={s.ghostContainer} onPress={() => { triggerHaptic('selection', { enabled: haptic }).catch(() => {}); const fs = ghost.sets[0]; if (fs) dispatch({ type: 'SET_INPUT', exIndex, weight: String(fs.weight), reps: String(fs.reps) }); }}>
13201321
<Text style={s.ghostLabel}>LAST · {ghost.date} · tap to fill</Text>
13211322
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>
1322-
{ghost.sets.map((gs, gi) => <Text key={gi} style={s.ghostSet}>{gs.weight > 0 ? `${gs.weight}kg` : 'BW'}×{gs.reps}</Text>)}
1323+
{ghost.sets.map((gs, gi) => <Text key={gi} style={s.ghostSet}>{gs.weight > 0 ? formatWeightFromKg(gs.weight, settings?.weightUnit || 'kg') : 'BW'}×{gs.reps}</Text>)}
13231324
</View>
13241325
</TouchableOpacity>
13251326
) : null}
13261327
{logged.length > 0 && (
13271328
<View style={s.loggedSets}>
13281329
{logged.map((ls, si) => (
13291330
<View key={ls.id}>
1330-
<SetRow set={ls} setIndex={si} exIndex={exIndex} dispatch={dispatch} effortTracking={settings.effortTracking || 'off'} hapticFeedback={haptic} />
1331+
<SetRow set={ls} setIndex={si} exIndex={exIndex} dispatch={dispatch} effortTracking={settings.effortTracking || 'off'} hapticFeedback={haptic} weightUnit={settings?.weightUnit || 'kg'} />
13311332
{showPlateUi && ls.type === 'warmup' && ls.weight > 0 && (
13321333
<Text style={s.plateHint}>
1333-
{getPlateText(ls.weight, activeGymProfile?.barWeight || settings.barWeight || 20, activeGymProfile?.plates)}
1334+
{getPlateText(ls.weight, activeGymProfile?.barWeight || settings.barWeight || 20, activeGymProfile?.plates, settings?.weightUnit || 'kg')}
13341335
</Text>
13351336
)}
13361337
</View>
13371338
))}
13381339
</View>
13391340
)}
1341+
{showPlateUi ? (
1342+
<TouchableOpacity
1343+
style={s.quickAddBtn}
1344+
onPress={() => {
1345+
triggerHaptic('selection', { enabled: haptic }).catch(() => {});
1346+
generateWarmups(exIndex);
1347+
}}
1348+
>
1349+
<Text style={s.quickAddText}>GENERATE WARM-UP SETS</Text>
1350+
</TouchableOpacity>
1351+
) : null}
13401352
<View style={s.inputRow}>
13411353
<View style={s.inputGroup}>
1342-
<Text style={s.inputLabel}>KG</Text>
1354+
<Text style={s.inputLabel}>{String(settings?.weightUnit || 'kg').toUpperCase()}</Text>
13431355
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
13441356
<TextInput style={s.input} value={inp.weight} onChangeText={t => dispatch({ type: 'SET_INPUT', exIndex, weight: t })} keyboardType="decimal-pad" placeholder="0" placeholderTextColor="#222" />
13451357
{showPlateUi ? (
@@ -1375,7 +1387,13 @@ function WorkoutContent({ plan, day, planIndex, dayIndex, navigation }) {
13751387
</View>
13761388

13771389
{/* Modals */}
1378-
<PlateModal visible={showPlates} targetKg={platesTarget} barWeight={activeGymProfile?.barWeight || settings.barWeight || 20} onClose={() => setShowPlates(false)} />
1390+
<PlateModal
1391+
visible={showPlates}
1392+
targetKg={platesTarget}
1393+
barWeight={activeGymProfile?.barWeight || settings.barWeight || 20}
1394+
weightUnit={settings?.weightUnit || 'kg'}
1395+
onClose={() => setShowPlates(false)}
1396+
/>
13791397

13801398
<RestOverrideModal
13811399
visible={showRestOverride}
@@ -1497,7 +1515,7 @@ function WorkoutContent({ plan, day, planIndex, dayIndex, navigation }) {
14971515
{t.action ? `${String(t.action).toUpperCase()} · ` : ''}{t.rationale || (t.plateau ? `Plateau: ${t.plateau}` : t.deload ? t.deload : 'Keep the trend moving')}
14981516
</Text>
14991517
</View>
1500-
<Text style={[s.targetVal, { color: t.action === 'reduce' ? '#FF8E8E' : colors.accent }]}>{t.weight}kg × {t.reps}</Text>
1518+
<Text style={[s.targetVal, { color: t.action === 'reduce' ? '#FF8E8E' : colors.accent }]}>{Number(t.weight || 0) > 0 ? formatWeightFromKg(t.weight, settings?.weightUnit || 'kg') : 'BW'} × {Math.max(1, Number(t.reps || 0))}</Text>
15011519
</View>
15021520
))}
15031521
</ScrollView>

src/screens/BodyMeasurementsScreen.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
1515
import { AppContext } from '../context/AppContext';
1616
import { useTheme } from '../context/ThemeContext';
1717
import { triggerHaptic } from '../services/hapticsEngine';
18+
import { convertUnitToKg, formatWeightFromKg } from '../utils/weightUnits';
1819

1920
const STORAGE_KEY = '@ironlog/bodyMeasurements';
2021
const SCREEN_WIDTH = Dimensions.get('window').width;
@@ -62,6 +63,7 @@ export default function BodyMeasurementsScreen() {
6263
const { bodyWeight, logBodyWeight, settings } = useContext(AppContext);
6364
const colors = useTheme();
6465
const haptic = settings?.hapticFeedback !== false;
66+
const weightUnit = settings?.weightUnit || 'kg';
6567

6668
const [activeTab, setActiveTab] = useState('WEIGHT');
6769
const [weightInput, setWeightInput] = useState('');
@@ -93,10 +95,13 @@ export default function BodyMeasurementsScreen() {
9395
// ── WEIGHT TAB ──────────────────────────────────────────────────────────────
9496

9597
const logWeight = () => {
96-
const w = parseFloat(weightInput);
97-
if (!w || w < 20 || w > 300) {
98+
const inputWeight = parseFloat(weightInput);
99+
const w = convertUnitToKg(inputWeight, weightUnit, 2);
100+
if (!inputWeight || w < 20 || w > 300) {
98101
triggerHaptic('invalidAction', { enabled: haptic }).catch(() => {});
99-
setAlertConfig({ title: 'Invalid weight', message: 'Enter a value between 20–300 kg.', buttons: [{ text: 'OK', style: 'default' }] });
102+
const min = formatWeightFromKg(20, weightUnit);
103+
const max = formatWeightFromKg(300, weightUnit);
104+
setAlertConfig({ title: 'Invalid weight', message: `Enter a value between ${min} and ${max}.`, buttons: [{ text: 'OK', style: 'default' }] });
100105
return;
101106
}
102107
logBodyWeight({ date: new Date().toISOString(), weight: w });
@@ -134,7 +139,7 @@ export default function BodyMeasurementsScreen() {
134139
<Polyline points={pts} fill="none" stroke={colors.accent} strokeWidth={2} />
135140
<Circle cx={lx} cy={ly} r={4} fill={colors.accent} />
136141
<SvgText x={lx} y={ly - 8} textAnchor="middle" fill={colors.text} fontSize={11}>
137-
{last.weight}kg
142+
{formatWeightFromKg(last.weight, weightUnit)}
138143
</SvgText>
139144
</Svg>
140145
);
@@ -258,7 +263,7 @@ export default function BodyMeasurementsScreen() {
258263
<ScrollView contentContainerStyle={s.tabContent}>
259264
{/* Input card */}
260265
<View style={[s.card, { backgroundColor: colors.card, borderColor: colors.cardBorder }]}>
261-
<Text style={[s.sectionLabel, { color: colors.muted }]}>TODAY'S WEIGHT (KG)</Text>
266+
<Text style={[s.sectionLabel, { color: colors.muted }]}>{`TODAY'S WEIGHT (${String(weightUnit).toUpperCase()})`}</Text>
262267
<View style={s.inputRow}>
263268
<TextInput
264269
style={[s.weightInput, { color: colors.text, borderBottomColor: colors.accent }]}
@@ -274,7 +279,7 @@ export default function BodyMeasurementsScreen() {
274279
</View>
275280
{delta !== null && (
276281
<Text style={[s.deltaText, { color: parseFloat(delta) <= 0 ? '#00C170' : '#FF4500' }]}>
277-
{parseFloat(delta) > 0 ? '+' : ''}{delta} kg vs last week
282+
{parseFloat(delta) > 0 ? '+' : ''}{formatWeightFromKg(Math.abs(Number(delta)), weightUnit)} vs last week
278283
</Text>
279284
)}
280285
</View>
@@ -293,7 +298,7 @@ export default function BodyMeasurementsScreen() {
293298
<Text style={[s.histDate, { color: colors.subtext }]}>
294299
{new Date(e.date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short' })}
295300
</Text>
296-
<Text style={[s.histWeight, { color: colors.text }]}>{e.weight} kg</Text>
301+
<Text style={[s.histWeight, { color: colors.text }]}>{formatWeightFromKg(e.weight, weightUnit)}</Text>
297302
</View>
298303
))}
299304
</ScrollView>

src/screens/ExerciseProgressScreen.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { AppContext } from '../context/AppContext';
88
import { useTheme } from '../context/ThemeContext';
99
import TrainingMaxCalculator from '../components/TrainingMaxCalculator';
1010
import { buildExerciseTrend } from '../domain/intelligence/performanceEngine';
11+
import { convertKgToUnit, formatWeightFromKg, formatVolumeFromKg } from '../utils/weightUnits';
1112

1213
const SCREEN_WIDTH = Dimensions.get('window').width;
1314
const CHART_WIDTH = SCREEN_WIDTH - 32;
@@ -188,8 +189,9 @@ function RangeSelector({ selected, onSelect, colors }) {
188189

189190
export default function ExerciseProgressScreen({ route }) {
190191
const { exerciseName } = route.params;
191-
const { history } = useContext(AppContext);
192+
const { history, settings } = useContext(AppContext);
192193
const colors = useTheme();
194+
const weightUnit = settings?.weightUnit || 'kg';
193195

194196
const [activeTab, setActiveTab] = useState(0);
195197
const [range, setRange] = useState('ALL');
@@ -212,7 +214,7 @@ export default function ExerciseProgressScreen({ route }) {
212214
return best;
213215
}, null);
214216
const summary = workingSets.length
215-
? workingSets.map((setItem) => `${setItem.reps || 0}x${setItem.weight || 0}kg`).join(', ')
217+
? workingSets.map((setItem) => `${setItem.reps || 0}x${formatWeightFromKg(setItem.weight || 0, weightUnit)}`).join(', ')
216218
: '-';
217219
return {
218220
date: session.date,
@@ -223,7 +225,7 @@ export default function ExerciseProgressScreen({ route }) {
223225
})
224226
.filter(Boolean)
225227
.sort((a, b) => new Date(b.date) - new Date(a.date));
226-
}, [exerciseName, history]);
228+
}, [exerciseName, history, weightUnit]);
227229

228230
const stats = useMemo(() => {
229231
if (!pointsInRange.length) {
@@ -247,23 +249,23 @@ export default function ExerciseProgressScreen({ route }) {
247249
}, [pointsInRange]);
248250

249251
const metricConfig = [
250-
{ key: 'e1rm', label: 'BEST EST. 1RM', unit: 'kg', values: pointsInRange.map((row) => row.e1rm), chart: 'line', stat: stats.bestE1rm },
251-
{ key: 'load', label: 'TOP LOAD', unit: 'kg', values: pointsInRange.map((row) => row.load), chart: 'line', stat: stats.bestLoad },
252+
{ key: 'e1rm', label: 'BEST EST. 1RM', unit: weightUnit, values: pointsInRange.map((row) => convertKgToUnit(row.e1rm || 0, weightUnit, weightUnit === 'lbs' ? 0 : 1)), chart: 'line', stat: convertKgToUnit(stats.bestE1rm, weightUnit, weightUnit === 'lbs' ? 0 : 1) },
253+
{ key: 'load', label: 'TOP LOAD', unit: weightUnit, values: pointsInRange.map((row) => convertKgToUnit(row.load || 0, weightUnit, weightUnit === 'lbs' ? 0 : 1)), chart: 'line', stat: convertKgToUnit(stats.bestLoad, weightUnit, weightUnit === 'lbs' ? 0 : 1) },
252254
{ key: 'reps', label: 'AVG REPS/SET', unit: '', values: pointsInRange.map((row) => row.reps), chart: 'line', stat: stats.avgReps },
253-
{ key: 'volume', label: 'SESSION VOLUME', unit: 'kg', values: pointsInRange.map((row) => row.volume), chart: 'bar', stat: stats.totalVolume },
255+
{ key: 'volume', label: 'SESSION VOLUME', unit: weightUnit, values: pointsInRange.map((row) => convertKgToUnit(row.volume || 0, weightUnit, 0)), chart: 'bar', stat: convertKgToUnit(stats.totalVolume, weightUnit, 0) },
254256
{ key: 'consistency', label: 'CONSISTENCY SCORE', unit: '%', values: pointsInRange.map((_, i) => Math.min(100, Math.round(((i + 1) / Math.max(1, pointsInRange.length)) * stats.consistency))), chart: 'line', stat: stats.consistency },
255257
];
256258

257259
const activeMetric = metricConfig[Math.min(activeTab, metricConfig.length - 1)];
258260

259261
const renderHistoryRow = ({ item }) => {
260-
const bestSetStr = item.bestSet ? `${item.bestSet.reps} x ${item.bestSet.weight}kg` : null;
262+
const bestSetStr = item.bestSet ? `${item.bestSet.reps} x ${formatWeightFromKg(item.bestSet.weight, weightUnit)}` : null;
261263
return (
262264
<View style={[styles.historyRow, { backgroundColor: colors.card, borderColor: colors.cardBorder }]}>
263265
<View style={styles.historyRowLeft}>
264266
<Text style={[styles.historyDate, { color: colors.text }]}>{formatDateFull(item.date)}</Text>
265267
<Text style={[styles.historySummary, { color: colors.subtext }]}>{item.summary}</Text>
266-
<Text style={[styles.historySub, { color: colors.muted }]}>Volume: {Math.round(item.volume)} kg</Text>
268+
<Text style={[styles.historySub, { color: colors.muted }]}>Volume: {formatVolumeFromKg(item.volume, weightUnit)}</Text>
267269
</View>
268270
{bestSetStr ? (
269271
<View style={[styles.bestSetBadge, { borderColor: colors.accent }]}>

0 commit comments

Comments
 (0)