From 5e93dc06c99cc88820f76e6750411b99135abbe1 Mon Sep 17 00:00:00 2001 From: Chris Sanden Date: Thu, 23 Apr 2026 16:09:24 +0200 Subject: [PATCH] Implemented cancel button for timer --- app/(tabs)/timer.tsx | 279 ++++++++++++++++++++++++++++++------------- 1 file changed, 197 insertions(+), 82 deletions(-) diff --git a/app/(tabs)/timer.tsx b/app/(tabs)/timer.tsx index fceb963..0a8f8cd 100644 --- a/app/(tabs)/timer.tsx +++ b/app/(tabs)/timer.tsx @@ -41,10 +41,13 @@ export default function App() { const [selectedIndex, setSelectedIndex] = React.useState(0) const [showCountdownText, setShowCountdownText] = React.useState(false) const scrollX = React.useRef(new Animated.Value(0)).current; - const timerAnimation = React.useRef(new Animated.Value(0)).current - const buttonAnimation = React.useRef(new Animated.Value(0)).current - const taskDetailsAnimation = React.useRef(new Animated.Value(0)).current - const countdownAnimation = React.useRef(new Animated.Value(0)).current + const timerAnimation = React.useRef(new Animated.Value(0)).current; + const buttonAnimation = React.useRef(new Animated.Value(0)).current; + const taskDetailsAnimation = React.useRef(new Animated.Value(0)).current; + const countdownAnimation = React.useRef(new Animated.Value(0)).current; + const countdownRef = React.useRef | null>(null); + const runningAnimationRef = React.useRef(null); + const cancelButtonAnimation = React.useRef(new Animated.Value(0)).current; const animation = React.useCallback(() => { if (isRunning || containerHeight === 0) { @@ -59,74 +62,99 @@ export default function App() { const totalSeconds = duration * TIMER_UNIT_IN_SECONDS; setTimeRemaining(totalSeconds); - const countdown = setInterval(() => { + if (countdownRef.current) { + clearInterval(countdownRef.current); + countdownRef.current = null; + } + countdownRef.current = setInterval(() => { setTimeRemaining((currentTime) => { if (currentTime <= 1) { - clearInterval(countdown) + if (countdownRef.current) { + clearInterval(countdownRef.current); + countdownRef.current = null; + } return 0; - } - return currentTime -1; - }); + } + return currentTime -1; + }); }, 1000); - Animated.sequence([ - Animated.parallel([ - Animated.timing(buttonAnimation, { - toValue: 1, - duration: 300, - useNativeDriver: true - }), - Animated.timing(taskDetailsAnimation, { - toValue: 1, - duration: 500, - useNativeDriver: true - }), - Animated.timing(countdownAnimation, { - toValue: 1, - duration: 300, - useNativeDriver: true + const runningAnimation = Animated.sequence([ + Animated.parallel([ + Animated.timing(buttonAnimation, { + toValue: 1, + duration: 300, + useNativeDriver: true }), - ]), - Animated.timing(timerAnimation, { + Animated.timing(cancelButtonAnimation, { + toValue: 1, + duration: 300, + useNativeDriver: true + }), + Animated.timing(taskDetailsAnimation, { + toValue: 1, + duration: 500, + useNativeDriver: true + }), + Animated.timing(countdownAnimation, { + toValue: 1, + duration: 300, + useNativeDriver: true + }), + ]), + Animated.timing(timerAnimation, { + toValue: 0, + duration: 300, + useNativeDriver: true + }), + Animated.timing(timerAnimation, { + toValue: containerHeight, + duration: totalSeconds * 1000, + useNativeDriver: true + }) + ]); + runningAnimationRef.current = runningAnimation; + + runningAnimation.start(({ finished }) => { + runningAnimationRef.current = null; + + if (!finished) { + return; + } + + Animated.timing(countdownAnimation, { + toValue: 0, + duration: 200, + useNativeDriver: true + }).start(() => { + setShowCountdownText(false); + + Animated.parallel([ + Animated.timing(buttonAnimation, { toValue: 0, duration: 300, useNativeDriver: true - }), - Animated.timing(timerAnimation, { - toValue: containerHeight, - duration: totalSeconds * 1000, - useNativeDriver: true - }) - ]).start(({ finished }) => { - if (!finished) { - setIsRunning(false); - return; - } - - Animated.timing(countdownAnimation, { - toValue: 0, - duration: 200, - useNativeDriver: true - }).start(() => { - setShowCountdownText(false); - - Animated.parallel([ - Animated.timing(buttonAnimation, { - toValue: 0, - duration: 300, - useNativeDriver: true }), Animated.timing(taskDetailsAnimation, { - toValue: 0, - duration: 300, - useNativeDriver: true + toValue: 0, + duration: 300, + useNativeDriver: true + }), + Animated.timing(cancelButtonAnimation, { + toValue: 0, + duration: 300, + useNativeDriver: true }), ]).start(() => { - setIsRunning(false); + setIsRunning(false); + /* TODO + Implement store and send of ellapsed time value in seconds to DB + for total time spent statistic + */ }) }) - }) -}, [countdownAnimation, buttonAnimation, containerHeight, duration, isRunning, taskDetailsAnimation, timerAnimation]) + }); +}, [cancelButtonAnimation, countdownAnimation, buttonAnimation, containerHeight, duration, isRunning, taskDetailsAnimation, timerAnimation]); React.useEffect(() => { if (containerHeight > 0 && !isRunning) { @@ -134,27 +162,77 @@ export default function App() { } }, [containerHeight, isRunning, timerAnimation]) - const opacity = buttonAnimation.interpolate({ + const cancelButtonOpacity = cancelButtonAnimation; + + const cancelButtonTranslateY = cancelButtonAnimation.interpolate({ inputRange: [0, 1], - outputRange: [1, 0] - }) - const translateY = buttonAnimation.interpolate({ - inputRange: [0, 1], - outputRange: [0, 200] - }) - const inactiveTimerNumberOpacity = buttonAnimation.interpolate({ - inputRange: [0, 1], - outputRange: [1, 0] - }) - const taskDetailsOpacity = taskDetailsAnimation.interpolate({ - inputRange: [0, 1], - outputRange: [0, 1] - }) - const taskDetailsTranslateY = taskDetailsAnimation.interpolate({ - inputRange: [0, 1], - outputRange: [20, 0] - }) - + outputRange: [16, 0], + }) + + const opacity = buttonAnimation.interpolate({ + inputRange: [0, 1], + outputRange: [1, 0] + }) + const translateY = buttonAnimation.interpolate({ + inputRange: [0, 1], + outputRange: [0, 200] + }) + const inactiveTimerNumberOpacity = buttonAnimation.interpolate({ + inputRange: [0, 1], + outputRange: [1, 0] + }) + const taskDetailsOpacity = taskDetailsAnimation.interpolate({ + inputRange: [0, 1], + outputRange: [0, 1] + }) + const taskDetailsTranslateY = taskDetailsAnimation.interpolate({ + inputRange: [0, 1], + outputRange: [20, 0] + }) + const cancelTimer = React.useCallback(() => { + if (!isRunning){ + return; + } + if (countdownRef.current) { + clearInterval(countdownRef.current); + countdownRef.current = null; + } + runningAnimationRef.current?.stop(); + runningAnimationRef.current = null; + + Animated.parallel([ + Animated.timing(cancelButtonAnimation, { + toValue: 0, + duration: 180, + useNativeDriver: true + }), + Animated.timing(buttonAnimation, { + toValue: 0, + duration: 220, + useNativeDriver: true + }), + Animated.timing(taskDetailsAnimation, { + toValue: 0, + duration: 220, + useNativeDriver: true + }), + Animated.timing(countdownAnimation, { + toValue: 0, + duration: 180, + useNativeDriver: true + }), + Animated.timing(timerAnimation, { + toValue: containerHeight, + duration: 220, + useNativeDriver: true + }), + ]).start(() => { + setShowCountdownText(false); + setTimeRemaining(0); + setIsRunning(false); + }); + }, [buttonAnimation, cancelButtonAnimation, containerHeight, countdownAnimation, timerAnimation, isRunning, taskDetailsAnimation,]); + return ( { @@ -184,12 +262,28 @@ return ( }] }, ]}> + - + + Start + + + + + + + Cancel +