From bf7fb431038b3c0ac4fe1cdcb67ff07d08a43f7e Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 4 Oct 2026 12:26:58 +0300 Subject: [PATCH 1/2] fix(common): prevent useTimer.pause from storing negative remaining time --- .../src/hooks/__tests__/useTimer.test.ts | 35 +++++++++++++++++++ packages/common/src/hooks/useTimer.ts | 6 ++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/common/src/hooks/__tests__/useTimer.test.ts b/packages/common/src/hooks/__tests__/useTimer.test.ts index 100531df47..a449b5137d 100644 --- a/packages/common/src/hooks/__tests__/useTimer.test.ts +++ b/packages/common/src/hooks/__tests__/useTimer.test.ts @@ -48,6 +48,41 @@ describe('useTimer', () => { expect(remainingTime).toBe(expectedRemainingTime); expect(clearTimeout).toHaveBeenCalledTimes(1); }); + + it('does not report a negative remaining time when pausing before starting', () => { + const { result } = renderHook(() => useTimer()); + + const remainingTime = result.current.pause(); + + expect(remainingTime).toBe(0); + expect(result.current.getRemainingTime()).toBe(0); + }); + + it('does not report a negative remaining time when pausing after the timer elapsed', () => { + const { result } = renderHook(() => useTimer()); + const callback = jest.fn(); + const duration = 1000; + + result.current.start(callback, duration); + jest.advanceTimersByTime(duration + 500); + + expect(result.current.pause()).toBe(0); + expect(result.current.getRemainingTime()).toBe(0); + }); + + it('resumes with a non-negative delay when pausing after the timer elapsed', () => { + const { result } = renderHook(() => useTimer()); + const callback = jest.fn(); + const duration = 1000; + + result.current.start(callback, duration); + jest.advanceTimersByTime(duration + 500); + result.current.pause(); + result.current.resume(); + + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + }); + it('resumes timer', async () => { const { result } = renderHook(() => useTimer()); const callback = jest.fn(); diff --git a/packages/common/src/hooks/useTimer.ts b/packages/common/src/hooks/useTimer.ts index c34827ea1b..9dbd4cc048 100644 --- a/packages/common/src/hooks/useTimer.ts +++ b/packages/common/src/hooks/useTimer.ts @@ -31,9 +31,11 @@ export const useTimer = () => { const pause = useCallback(() => { if (!isPausedRef.current) { - const currentTime = new Date().getTime(); + // Elapsed time is only meaningful while a timer is running, and a timer can never + // have negative time remaining. Clamping keeps `pause` from storing a negative value. + const elapsedTime = timerRef.current ? Date.now() - startTimeRef.current : 0; clear(); - remainingTimeRef.current -= currentTime - startTimeRef.current; + remainingTimeRef.current = Math.max(0, remainingTimeRef.current - elapsedTime); isPausedRef.current = true; } return remainingTimeRef.current; From c2dd762b50bf3af37740e589ef47e0e26e70fc80 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 4 Oct 2026 12:39:45 +0300 Subject: [PATCH 2/2] fix(mobile): forward lottie pause, resume, and reset to the mounted instance --- packages/mobile/src/animation/createLottie.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/mobile/src/animation/createLottie.tsx b/packages/mobile/src/animation/createLottie.tsx index 01cf93b6bf..580776a0f5 100644 --- a/packages/mobile/src/animation/createLottie.tsx +++ b/packages/mobile/src/animation/createLottie.tsx @@ -30,8 +30,6 @@ export type LottiePlayerMobile = { }; } & LottiePlayer; -const noop = () => {}; - export const createLottie = ( source: Source, progressOverride?: Animated.Value, @@ -104,9 +102,9 @@ export const createLottie = ( return { play, playMarkers, - pause: lottieRef.current?.pause ?? noop, - resume: lottieRef.current?.resume ?? noop, - reset: lottieRef.current?.reset ?? noop, + pause: () => lottieRef.current?.pause(), + resume: () => lottieRef.current?.resume(), + reset: () => lottieRef.current?.reset(), progress: { value: progress, timing: setProgressTiming,