2025-02-24 22:27:01 -05:00
|
|
|
|
// components/workout/SetInput.tsx
|
|
|
|
|
import React, { useState, useCallback } from 'react';
|
|
|
|
|
import { View, TextInput, TouchableOpacity } from 'react-native';
|
|
|
|
|
import { Text } from '@/components/ui/text';
|
2025-03-02 13:23:28 -05:00
|
|
|
|
import { Circle, CheckCircle } from 'lucide-react-native'; // Lucide React icons
|
2025-02-24 22:27:01 -05:00
|
|
|
|
import { cn } from '@/lib/utils';
|
|
|
|
|
import { useWorkoutStore } from '@/stores/workoutStore';
|
2025-03-12 19:23:28 -04:00
|
|
|
|
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
2025-02-24 22:27:01 -05:00
|
|
|
|
import type { WorkoutSet } from '@/types/workout';
|
|
|
|
|
import debounce from 'lodash/debounce';
|
|
|
|
|
|
|
|
|
|
interface SetInputProps {
|
|
|
|
|
exerciseIndex: number;
|
|
|
|
|
setIndex: number;
|
|
|
|
|
setNumber: number;
|
|
|
|
|
weight?: number;
|
|
|
|
|
reps?: number;
|
|
|
|
|
isCompleted?: boolean;
|
|
|
|
|
previousSet?: WorkoutSet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function SetInput({
|
|
|
|
|
exerciseIndex,
|
|
|
|
|
setIndex,
|
|
|
|
|
setNumber,
|
|
|
|
|
weight = 0,
|
|
|
|
|
reps = 0,
|
|
|
|
|
isCompleted = false,
|
|
|
|
|
previousSet
|
|
|
|
|
}: SetInputProps) {
|
2025-03-01 13:43:42 -05:00
|
|
|
|
// Get theme colors
|
|
|
|
|
const { isDarkColorScheme } = useColorScheme();
|
|
|
|
|
|
2025-02-24 22:27:01 -05:00
|
|
|
|
// Local state for controlled inputs
|
|
|
|
|
const [weightValue, setWeightValue] = useState(weight.toString());
|
|
|
|
|
const [repsValue, setRepsValue] = useState(reps.toString());
|
|
|
|
|
|
|
|
|
|
// Get actions from store
|
|
|
|
|
const { updateSet, completeSet } = useWorkoutStore.getState();
|
|
|
|
|
|
|
|
|
|
// Debounced update functions to prevent too many state updates
|
|
|
|
|
const debouncedUpdateWeight = useCallback(
|
|
|
|
|
debounce((value: number) => {
|
|
|
|
|
updateSet(exerciseIndex, setIndex, { weight: value });
|
|
|
|
|
}, 500),
|
|
|
|
|
[exerciseIndex, setIndex]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const debouncedUpdateReps = useCallback(
|
|
|
|
|
debounce((value: number) => {
|
|
|
|
|
updateSet(exerciseIndex, setIndex, { reps: value });
|
|
|
|
|
}, 500),
|
|
|
|
|
[exerciseIndex, setIndex]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleWeightChange = (value: string) => {
|
|
|
|
|
if (value === '' || /^\d*\.?\d*$/.test(value)) {
|
|
|
|
|
setWeightValue(value);
|
|
|
|
|
const numValue = parseFloat(value);
|
|
|
|
|
if (!isNaN(numValue)) {
|
|
|
|
|
debouncedUpdateWeight(numValue);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleRepsChange = (value: string) => {
|
|
|
|
|
if (value === '' || /^\d*$/.test(value)) {
|
|
|
|
|
setRepsValue(value);
|
|
|
|
|
const numValue = parseInt(value, 10);
|
|
|
|
|
if (!isNaN(numValue)) {
|
|
|
|
|
debouncedUpdateReps(numValue);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleCompleteSet = useCallback(() => {
|
|
|
|
|
completeSet(exerciseIndex, setIndex);
|
2025-03-01 13:43:42 -05:00
|
|
|
|
}, [exerciseIndex, setIndex, completeSet]);
|
2025-02-24 22:27:01 -05:00
|
|
|
|
|
|
|
|
|
const handleCopyPreviousWeight = useCallback(() => {
|
|
|
|
|
if (previousSet?.weight) {
|
|
|
|
|
handleWeightChange(previousSet.weight.toString());
|
|
|
|
|
}
|
|
|
|
|
}, [previousSet]);
|
|
|
|
|
|
|
|
|
|
const handleCopyPreviousReps = useCallback(() => {
|
|
|
|
|
if (previousSet?.reps) {
|
|
|
|
|
handleRepsChange(previousSet.reps.toString());
|
|
|
|
|
}
|
|
|
|
|
}, [previousSet]);
|
|
|
|
|
|
2025-03-01 13:43:42 -05:00
|
|
|
|
// Get the appropriate colors based on theme variables
|
|
|
|
|
const purpleColor = 'hsl(261, 90%, 66%)'; // --purple from your constants
|
|
|
|
|
const mutedForegroundColor = isDarkColorScheme
|
|
|
|
|
? 'hsl(240, 5%, 64.9%)' // --muted-foreground dark
|
|
|
|
|
: 'hsl(240, 3.8%, 46.1%)'; // --muted-foreground light
|
|
|
|
|
|
2025-02-24 22:27:01 -05:00
|
|
|
|
return (
|
|
|
|
|
<View className={cn(
|
2025-03-02 13:23:28 -05:00
|
|
|
|
"flex-row items-center px-4 py-1 border-b border-border",
|
2025-02-24 22:27:01 -05:00
|
|
|
|
isCompleted && "bg-primary/5"
|
|
|
|
|
)}>
|
|
|
|
|
{/* Set Number */}
|
2025-03-02 13:23:28 -05:00
|
|
|
|
<Text className="w-8 text-sm font-medium text-muted-foreground text-center">
|
2025-02-24 22:27:01 -05:00
|
|
|
|
{setNumber}
|
|
|
|
|
</Text>
|
|
|
|
|
|
|
|
|
|
{/* Previous Set */}
|
|
|
|
|
<Text className="w-20 text-sm text-center text-muted-foreground">
|
|
|
|
|
{previousSet ? `${previousSet.weight}×${previousSet.reps}` : '—'}
|
|
|
|
|
</Text>
|
|
|
|
|
|
|
|
|
|
{/* Weight Input */}
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
className="flex-1 mx-1"
|
|
|
|
|
activeOpacity={0.7}
|
|
|
|
|
onLongPress={handleCopyPreviousWeight}
|
|
|
|
|
>
|
|
|
|
|
<TextInput
|
|
|
|
|
className={cn(
|
2025-03-02 13:23:28 -05:00
|
|
|
|
"h-8 px-3 rounded-md text-center text-foreground",
|
2025-02-24 22:27:01 -05:00
|
|
|
|
"bg-secondary border border-border",
|
|
|
|
|
isCompleted && "bg-primary/10 border-primary/20"
|
|
|
|
|
)}
|
|
|
|
|
value={weightValue === '0' ? '' : weightValue}
|
|
|
|
|
onChangeText={handleWeightChange}
|
|
|
|
|
keyboardType="decimal-pad"
|
|
|
|
|
placeholder="0"
|
2025-03-01 13:43:42 -05:00
|
|
|
|
placeholderTextColor={mutedForegroundColor}
|
2025-02-24 22:27:01 -05:00
|
|
|
|
selectTextOnFocus
|
|
|
|
|
/>
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
|
|
|
|
{/* Reps Input */}
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
className="flex-1 mx-1"
|
|
|
|
|
activeOpacity={0.7}
|
|
|
|
|
onLongPress={handleCopyPreviousReps}
|
|
|
|
|
>
|
|
|
|
|
<TextInput
|
|
|
|
|
className={cn(
|
2025-03-02 13:23:28 -05:00
|
|
|
|
"h-8 px-3 rounded-md text-center text-foreground",
|
2025-02-24 22:27:01 -05:00
|
|
|
|
"bg-secondary border border-border",
|
|
|
|
|
isCompleted && "bg-primary/10 border-primary/20"
|
|
|
|
|
)}
|
|
|
|
|
value={repsValue === '0' ? '' : repsValue}
|
|
|
|
|
onChangeText={handleRepsChange}
|
|
|
|
|
keyboardType="number-pad"
|
|
|
|
|
placeholder="0"
|
2025-03-01 13:43:42 -05:00
|
|
|
|
placeholderTextColor={mutedForegroundColor}
|
2025-02-24 22:27:01 -05:00
|
|
|
|
selectTextOnFocus
|
|
|
|
|
/>
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
2025-03-02 13:23:28 -05:00
|
|
|
|
{/* Complete Button using Lucide React icons - without fill */}
|
2025-03-01 13:43:42 -05:00
|
|
|
|
<TouchableOpacity
|
|
|
|
|
className="w-10 h-10 items-center justify-center"
|
2025-02-24 22:27:01 -05:00
|
|
|
|
onPress={handleCompleteSet}
|
|
|
|
|
>
|
2025-03-02 13:23:28 -05:00
|
|
|
|
{isCompleted ? (
|
|
|
|
|
<CheckCircle
|
|
|
|
|
size={24}
|
|
|
|
|
color={purpleColor}
|
|
|
|
|
// Removed fill and fillOpacity properties
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<Circle
|
|
|
|
|
size={24}
|
|
|
|
|
color={mutedForegroundColor}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2025-03-01 13:43:42 -05:00
|
|
|
|
</TouchableOpacity>
|
2025-02-24 22:27:01 -05:00
|
|
|
|
</View>
|
|
|
|
|
);
|
|
|
|
|
}
|