feat: add prepation time to timers
parent
2e44837381
commit
5de377f0c3
|
|
@ -21,22 +21,26 @@ const t = i18n.scoped('dashboard');
|
|||
|
||||
export default function Dashboard() {
|
||||
const navigation = useNavigation<NavigationProp>();
|
||||
const { timerState, isLoading, updateReps, updateWorkTime, updateRestTime } = useTimerContext();
|
||||
const { timerState, isLoading, updateReps, updateWorkTime, updateRestTime, updatePreparationTime } = useTimerContext();
|
||||
|
||||
const [showWorkTimePicker, setShowWorkTimePicker] = useState<boolean>(false);
|
||||
const [showPreparationTimePicker, setShowPreparationTimePicker] = useState<boolean>(false);
|
||||
const [showRestTimePicker, setShowRestTimePicker] = useState<boolean>(false);
|
||||
|
||||
// Local variables for UI changes
|
||||
const [localPreparationTime, setLocalPreparationTime] = useState<number>(timerState.preparationTime);
|
||||
const [localReps, setLocalReps] = useState<number>(timerState.reps);
|
||||
const [localWorkTime, setLocalWorkTime] = useState<number>(timerState.workTime);
|
||||
const [localRestTime, setLocalRestTime] = useState<number>(timerState.restTime);
|
||||
|
||||
// Update local states when store data is loaded
|
||||
useEffect(() => {
|
||||
console.log('timerState', timerState);
|
||||
if (!isLoading) {
|
||||
setLocalReps(timerState.reps);
|
||||
setLocalWorkTime(timerState.workTime);
|
||||
setLocalRestTime(timerState.restTime);
|
||||
setLocalPreparationTime(timerState.preparationTime);
|
||||
}
|
||||
}, [isLoading, timerState]);
|
||||
|
||||
|
|
@ -64,13 +68,35 @@ export default function Dashboard() {
|
|||
<VerticalSpacer heightUnits={8} />
|
||||
|
||||
<CardContainer>
|
||||
<Card backgroundColor="black">
|
||||
<Card backgroundColor="grey" onPress={() => setShowPreparationTimePicker(true)}>
|
||||
<CardTextContainer>
|
||||
<CustomText>{t('repetition')}</CustomText>
|
||||
<NumberSelector reps={localReps} setReps={handleRepsChange} />
|
||||
<BlackCustomText>{t('preparation')}</BlackCustomText>
|
||||
<BlackCustomText>{formatTime(localPreparationTime)}</BlackCustomText>
|
||||
</CardTextContainer>
|
||||
</Card>
|
||||
|
||||
<TimerPickerModal
|
||||
hideHours
|
||||
visible={showPreparationTimePicker}
|
||||
setIsVisible={setShowPreparationTimePicker}
|
||||
onConfirm={(pickedDuration: any) => {
|
||||
const newPreparationTime = pickedDuration.minutes * 60 + pickedDuration.seconds;
|
||||
setLocalPreparationTime(newPreparationTime);
|
||||
updatePreparationTime(newPreparationTime);
|
||||
setShowPreparationTimePicker(false);
|
||||
}}
|
||||
modalTitle={t('setPreparationTime')}
|
||||
onCancel={() => setShowPreparationTimePicker(false)}
|
||||
closeOnOverlayPress
|
||||
styles={{
|
||||
theme: "dark",
|
||||
}}
|
||||
initialValue={{ minutes: Math.floor(timerState.preparationTime / 60), seconds: timerState.preparationTime % 60 }}
|
||||
modalProps={{
|
||||
overlayOpacity: 0.2,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Card backgroundColor="red" onPress={() => setShowWorkTimePicker(true)}>
|
||||
<CardTextContainer>
|
||||
<CustomText>{t('fight')}</CustomText>
|
||||
|
|
@ -128,6 +154,13 @@ export default function Dashboard() {
|
|||
overlayOpacity: 0.2,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Card backgroundColor="black">
|
||||
<CardTextContainer>
|
||||
<CustomText>{t('repetition')}</CustomText>
|
||||
<NumberSelector reps={localReps} setReps={handleRepsChange} />
|
||||
</CardTextContainer>
|
||||
</Card>
|
||||
</CardContainer>
|
||||
|
||||
<VerticalSpacer heightUnits={5} />
|
||||
|
|
@ -179,3 +212,7 @@ const CustomText = styled(Text)(({ theme }) => ({
|
|||
textAlign: 'center',
|
||||
color: theme.colors.fixed.white
|
||||
}))
|
||||
|
||||
const BlackCustomText = styled(CustomText)(({ theme }) => ({
|
||||
color: theme.colors.fixed.black
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export default function Timer() {
|
|||
const [isWorkPhase, setIsWorkPhase] = useState<boolean>(true);
|
||||
const [isRunning, setIsRunning] = useState<boolean>(false);
|
||||
const [isFinish, setIsFinish] = useState<boolean>(false);
|
||||
const [isPreparationPhase, setIsPreparationPhase] = useState<boolean>(false);
|
||||
const [soundEnabled, setSoundEnabled] = useState<boolean>(true);
|
||||
const { playSound } = useAudio(
|
||||
require("../assets/audios/boxingBell.mp3"),
|
||||
|
|
@ -75,13 +76,25 @@ export default function Timer() {
|
|||
timerId = BackgroundTimer.setInterval(() => {
|
||||
const newTime = timeLeft - 1;
|
||||
setTimeLeft(newTime);
|
||||
|
||||
let phaseText = "Repos";
|
||||
if (isPreparationPhase) {
|
||||
phaseText = "Préparation";
|
||||
} else if (isWorkPhase) {
|
||||
phaseText = "Travail";
|
||||
}
|
||||
|
||||
updateNotification(
|
||||
"Timer en cours",
|
||||
`Phase: ${isWorkPhase ? "Travail" : "Repos"}, Temps restant: ${newTime}s`,
|
||||
`Phase: ${phaseText}, Temps restant: ${newTime}s`,
|
||||
);
|
||||
}, 1000);
|
||||
} else if (isRunning && timeLeft === 0) {
|
||||
nextRep();
|
||||
if (isPreparationPhase) {
|
||||
startFirstRep();
|
||||
} else {
|
||||
nextRep();
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
|
|
@ -89,23 +102,43 @@ export default function Timer() {
|
|||
BackgroundTimer.clearInterval(timerId);
|
||||
}
|
||||
};
|
||||
}, [isRunning, timeLeft]);
|
||||
}, [isRunning, timeLeft, isPreparationPhase]);
|
||||
|
||||
const handleStart = () => {
|
||||
// Démarrer avec la phase de préparation si elle est configurée
|
||||
if (timerState.preparationTime > 0) {
|
||||
setIsPreparationPhase(true);
|
||||
setCurrentRep(0);
|
||||
setTimeLeft(timerState.preparationTime);
|
||||
} else {
|
||||
setIsPreparationPhase(false);
|
||||
setCurrentRep(1);
|
||||
setIsWorkPhase(true);
|
||||
setTimeLeft(timerState.workTime);
|
||||
}
|
||||
|
||||
setIsRunning(true);
|
||||
setIsFinish(false);
|
||||
|
||||
const phaseText = timerState.preparationTime > 0 ? "Préparation" : "Travail";
|
||||
updateNotification(
|
||||
"Timer en cours",
|
||||
`Phase: ${phaseText}, Temps restant: ${timeLeft}s`,
|
||||
);
|
||||
};
|
||||
|
||||
const startFirstRep = () => {
|
||||
playSound();
|
||||
setIsPreparationPhase(false);
|
||||
setCurrentRep(1);
|
||||
setIsWorkPhase(true);
|
||||
setTimeLeft(timerState.workTime);
|
||||
setIsRunning(true);
|
||||
setIsFinish(false);
|
||||
updateNotification(
|
||||
"Timer en cours",
|
||||
`Phase: ${isWorkPhase ? "Travail" : "Repos"}, Temps restant: ${timeLeft}s`,
|
||||
);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setCurrentRep(0);
|
||||
setIsWorkPhase(true);
|
||||
setIsPreparationPhase(false);
|
||||
setTimeLeft(timerState.workTime);
|
||||
setIsRunning(false);
|
||||
setIsFinish(false);
|
||||
|
|
@ -155,6 +188,7 @@ export default function Timer() {
|
|||
|
||||
const renderBgColor: () => TimerBgColor = () => {
|
||||
if (isFinish) return "black";
|
||||
if (isPreparationPhase) return "grey";
|
||||
if (isWorkPhase) return "red";
|
||||
|
||||
return "green";
|
||||
|
|
@ -167,6 +201,7 @@ export default function Timer() {
|
|||
{!isFinish && (
|
||||
<TimerContent
|
||||
isWorkPhase={isWorkPhase}
|
||||
isPreparationPhase={isPreparationPhase}
|
||||
timeLeft={timeLeft}
|
||||
reps={timerState.reps}
|
||||
bgColor={renderBgColor()}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ export const en = {
|
|||
repetition: 'Reps',
|
||||
fight: 'Fight time',
|
||||
rest: 'Rest',
|
||||
preparation: 'Preparation',
|
||||
totalTime: 'Total time',
|
||||
begin: 'Start',
|
||||
settings: 'Settings',
|
||||
setWorkTime: 'Set fight time',
|
||||
setRestTime: 'Set rest time',
|
||||
setPreparationTime: 'Set preparation time',
|
||||
},
|
||||
settings: {
|
||||
title: 'Settings',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ export const fr = {
|
|||
back: 'Retour',
|
||||
dashboard: {
|
||||
repetition: 'Reps.',
|
||||
preparation: 'Préparation',
|
||||
fight: 'Temps du combat',
|
||||
rest: 'Repos',
|
||||
totalTime: 'Temps total',
|
||||
|
|
@ -10,6 +11,7 @@ export const fr = {
|
|||
settings: 'Paramètres',
|
||||
setWorkTime: 'Définir le temps de combat',
|
||||
setRestTime: 'Définir le temps de repos',
|
||||
setPreparationTime: 'Définir le temps de préparation',
|
||||
},
|
||||
settings: {
|
||||
title: 'Paramètres',
|
||||
|
|
@ -21,6 +23,8 @@ export const fr = {
|
|||
timer: {
|
||||
timerContent: {
|
||||
fight: 'Combat',
|
||||
preparation: 'Préparation',
|
||||
preparationDescription: 'Commencez à vous préparer',
|
||||
rest: 'Repos',
|
||||
stop: 'Arrêter',
|
||||
start: 'Commencer',
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ interface TimerContextType {
|
|||
updateReps: (reps: number) => Promise<void>;
|
||||
updateWorkTime: (workTime: number) => Promise<void>;
|
||||
updateRestTime: (restTime: number) => Promise<void>;
|
||||
updatePreparationTime: (preparationTime: number) => Promise<void>;
|
||||
updateState: (state: Partial<TimerState>) => Promise<void>;
|
||||
}
|
||||
|
||||
|
|
@ -16,12 +17,14 @@ const defaultContextValue: TimerContextType = {
|
|||
timerState: {
|
||||
reps: 1,
|
||||
workTime: 0,
|
||||
restTime: 0
|
||||
restTime: 0,
|
||||
preparationTime: 3
|
||||
},
|
||||
isLoading: true,
|
||||
updateReps: async () => {},
|
||||
updateWorkTime: async () => {},
|
||||
updateRestTime: async () => {},
|
||||
updatePreparationTime: async () => {},
|
||||
updateState: async () => {}
|
||||
};
|
||||
|
||||
|
|
@ -41,7 +44,8 @@ export const TimerProvider: React.FC<TimerProviderProps> = ({ children }) => {
|
|||
const [timerState, setTimerState] = useState<TimerState>({
|
||||
reps: 1,
|
||||
workTime: 0,
|
||||
restTime: 0
|
||||
restTime: 0,
|
||||
preparationTime: 3
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
|
|
@ -71,6 +75,14 @@ export const TimerProvider: React.FC<TimerProviderProps> = ({ children }) => {
|
|||
}
|
||||
};
|
||||
|
||||
const updatePreparationTime = async (preparationTime: number) => {
|
||||
try {
|
||||
await timerStore.savePreparationTime(preparationTime);
|
||||
setTimerState(prev => ({ ...prev, preparationTime }));
|
||||
} catch (error) {
|
||||
console.error('Error updating preparation time:', error);
|
||||
}
|
||||
}
|
||||
// Update work time
|
||||
const updateWorkTime = async (workTime: number) => {
|
||||
try {
|
||||
|
|
@ -108,6 +120,7 @@ export const TimerProvider: React.FC<TimerProviderProps> = ({ children }) => {
|
|||
updateReps,
|
||||
updateWorkTime,
|
||||
updateRestTime,
|
||||
updatePreparationTime,
|
||||
updateState
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
|
|||
const TIMER_KEYS = {
|
||||
REPS: 'timer_reps',
|
||||
WORK_TIME: 'timer_work_time',
|
||||
REST_TIME: 'timer_rest_time'
|
||||
REST_TIME: 'timer_rest_time',
|
||||
PREPARATION_TIME: 'timer_preparation_time'
|
||||
};
|
||||
|
||||
// Store interface
|
||||
|
|
@ -12,13 +13,15 @@ export interface TimerState {
|
|||
reps: number;
|
||||
workTime: number;
|
||||
restTime: number;
|
||||
preparationTime: number;
|
||||
}
|
||||
|
||||
// Default values
|
||||
const DEFAULT_STATE: TimerState = {
|
||||
reps: 1,
|
||||
workTime: 0,
|
||||
restTime: 0
|
||||
restTime: 0,
|
||||
preparationTime: 3
|
||||
};
|
||||
|
||||
// Store management class
|
||||
|
|
@ -32,6 +35,14 @@ class TimerStore {
|
|||
}
|
||||
}
|
||||
|
||||
async savePreparationTime(preparationTime: number): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem(TIMER_KEYS.PREPARATION_TIME, preparationTime.toString());
|
||||
} catch (error) {
|
||||
console.error('Error saving preparation time:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Method to save work time
|
||||
async saveWorkTime(workTime: number): Promise<void> {
|
||||
try {
|
||||
|
|
@ -56,7 +67,8 @@ class TimerStore {
|
|||
await Promise.all([
|
||||
this.saveReps(state.reps),
|
||||
this.saveWorkTime(state.workTime),
|
||||
this.saveRestTime(state.restTime)
|
||||
this.saveRestTime(state.restTime),
|
||||
this.savePreparationTime(state.preparationTime)
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('Error saving state:', error);
|
||||
|
|
@ -74,6 +86,16 @@ class TimerStore {
|
|||
}
|
||||
}
|
||||
|
||||
async getPreparationTime(): Promise<number> {
|
||||
try {
|
||||
const value = await AsyncStorage.getItem(TIMER_KEYS.PREPARATION_TIME);
|
||||
return value !== null ? parseInt(value, 10) : DEFAULT_STATE.preparationTime;
|
||||
} catch (error) {
|
||||
console.error('Error retrieving preparation time:', error);
|
||||
return DEFAULT_STATE.preparationTime;
|
||||
}
|
||||
}
|
||||
|
||||
// Method to retrieve work time
|
||||
async getWorkTime(): Promise<number> {
|
||||
try {
|
||||
|
|
@ -99,16 +121,18 @@ class TimerStore {
|
|||
// Method to retrieve complete state
|
||||
async getState(): Promise<TimerState> {
|
||||
try {
|
||||
const [reps, workTime, restTime] = await Promise.all([
|
||||
const [reps, workTime, restTime, preparationTime] = await Promise.all([
|
||||
this.getReps(),
|
||||
this.getWorkTime(),
|
||||
this.getRestTime()
|
||||
this.getRestTime(),
|
||||
this.getPreparationTime()
|
||||
]);
|
||||
|
||||
return {
|
||||
reps,
|
||||
workTime,
|
||||
restTime
|
||||
restTime,
|
||||
preparationTime
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error retrieving state:', error);
|
||||
|
|
|
|||
|
|
@ -102,7 +102,8 @@ describe('TimerStore', () => {
|
|||
const state: TimerState = {
|
||||
reps: 3,
|
||||
workTime: 40,
|
||||
restTime: 20
|
||||
restTime: 20,
|
||||
preparationTime: 3
|
||||
};
|
||||
|
||||
await timerStore.saveState(state);
|
||||
|
|
@ -233,7 +234,8 @@ describe('TimerStore', () => {
|
|||
expect(result).toEqual({
|
||||
reps: 3,
|
||||
workTime: 40,
|
||||
restTime: 20
|
||||
restTime: 20,
|
||||
preparationTime: 3
|
||||
});
|
||||
|
||||
expect(mockedAsyncStorage.getItem).toHaveBeenCalledWith('timer_reps');
|
||||
|
|
@ -250,7 +252,8 @@ describe('TimerStore', () => {
|
|||
expect(result).toEqual({
|
||||
reps: 1,
|
||||
workTime: 0,
|
||||
restTime: 0
|
||||
restTime: 0,
|
||||
preparationTime: 3
|
||||
}); // Default values
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export type TimerBgColor = 'red' | 'green' | 'black';
|
||||
export type TimerBgColor = 'red' | 'green' | 'black' | 'grey';
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { i18n } from '@/app/i18n/i18n';
|
|||
|
||||
interface TimerContentProps {
|
||||
isWorkPhase: boolean;
|
||||
isPreparationPhase?: boolean;
|
||||
timeLeft: number;
|
||||
reps: number;
|
||||
currentRep: number;
|
||||
|
|
@ -26,6 +27,7 @@ const t = i18n.scoped('timer.timerContent');
|
|||
|
||||
export default function TimerContent({
|
||||
isWorkPhase,
|
||||
isPreparationPhase = false,
|
||||
timeLeft,
|
||||
reps,
|
||||
currentRep,
|
||||
|
|
@ -44,24 +46,40 @@ export default function TimerContent({
|
|||
handleReset();
|
||||
};
|
||||
|
||||
const getTitle = () => {
|
||||
if (isPreparationPhase) return t('preparation');
|
||||
if (isWorkPhase) return t('fight');
|
||||
return t('rest');
|
||||
};
|
||||
|
||||
// Handler for disabled buttons during preparation phase
|
||||
const handleNoop = () => {
|
||||
// Ne fait rien si on est en phase de préparation
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>{isWorkPhase ? t('fight') : t('rest')}</Title>
|
||||
<Title isPreparationPhase={isPreparationPhase}>{getTitle()}</Title>
|
||||
|
||||
<VerticalSpacer heightUnits={8} />
|
||||
|
||||
<Time>
|
||||
<Time isPreparationPhase={isPreparationPhase}>
|
||||
{formatTime(timeLeft)}
|
||||
</Time>
|
||||
|
||||
<VerticalSpacer heightUnits={8} />
|
||||
|
||||
<Reps>{currentRep} / {reps}</Reps>
|
||||
{!isPreparationPhase && <Reps>{currentRep} / {reps}</Reps>}
|
||||
{isPreparationPhase && <Reps isPreparationPhase={true}>{t('preparationDescription')}</Reps>}
|
||||
|
||||
<VerticalSpacer heightUnits={8} />
|
||||
|
||||
<ButtonContainer bgColor={bgColor}>
|
||||
<Button label={t('prev')} onPress={previousRep} />
|
||||
<Button
|
||||
label={t('prev')}
|
||||
onPress={isPreparationPhase ? handleNoop : previousRep}
|
||||
status={isPreparationPhase ? 'disabled' : 'ready'}
|
||||
/>
|
||||
|
||||
<ElasticSpacer />
|
||||
|
||||
|
|
@ -73,7 +91,11 @@ export default function TimerContent({
|
|||
|
||||
<ElasticSpacer />
|
||||
|
||||
<Button label={t('next')} onPress={nextRep} />
|
||||
<Button
|
||||
label={t('next')}
|
||||
onPress={isPreparationPhase ? nextRep : nextRep}
|
||||
status="ready"
|
||||
/>
|
||||
</ButtonContainer>
|
||||
|
||||
<VerticalSpacer heightUnits={4} />
|
||||
|
|
@ -89,20 +111,20 @@ const ButtonContainer = styled(View)<{ bgColor: TimerBgColor }>(({ theme, bgColo
|
|||
justifyContent: 'space-between',
|
||||
}));
|
||||
|
||||
const Title = styled(Text)(({ theme }) => ({
|
||||
const Title = styled(Text)<{ isPreparationPhase?: boolean }>(({ theme, isPreparationPhase = false }) => ({
|
||||
fontSize: 50,
|
||||
fontWeight: 'bold',
|
||||
color: theme.colors.fixed.white
|
||||
color: isPreparationPhase ? theme.colors.fixed.black : theme.colors.fixed.white
|
||||
}));
|
||||
|
||||
const Time = styled(Text)(({ theme }) => ({
|
||||
const Time = styled(Text)<{ isPreparationPhase?: boolean }>(({ theme, isPreparationPhase = false }) => ({
|
||||
fontSize: 100,
|
||||
fontWeight: 'bold',
|
||||
color: theme.colors.fixed.white
|
||||
color: isPreparationPhase ? theme.colors.fixed.black : theme.colors.fixed.white
|
||||
}));
|
||||
|
||||
const Reps = styled(Text)(({ theme }) => ({
|
||||
const Reps = styled(Text)<{ isPreparationPhase?: boolean }>(({ theme, isPreparationPhase = false }) => ({
|
||||
fontSize: 30,
|
||||
fontWeight: 'bold',
|
||||
color: theme.colors.fixed.white
|
||||
color: isPreparationPhase ? theme.colors.fixed.black : theme.colors.fixed.white
|
||||
}));
|
||||
|
|
|
|||
Loading…
Reference in New Issue