Implemented cancel button for timer
This commit is contained in:
@@ -41,10 +41,13 @@ export default function App() {
|
|||||||
const [selectedIndex, setSelectedIndex] = React.useState(0)
|
const [selectedIndex, setSelectedIndex] = React.useState(0)
|
||||||
const [showCountdownText, setShowCountdownText] = React.useState(false)
|
const [showCountdownText, setShowCountdownText] = React.useState(false)
|
||||||
const scrollX = React.useRef(new Animated.Value(0)).current;
|
const scrollX = React.useRef(new Animated.Value(0)).current;
|
||||||
const timerAnimation = 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 buttonAnimation = React.useRef(new Animated.Value(0)).current;
|
||||||
const taskDetailsAnimation = 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 countdownAnimation = React.useRef(new Animated.Value(0)).current;
|
||||||
|
const countdownRef = React.useRef<ReturnType<typeof setInterval> | null>(null);
|
||||||
|
const runningAnimationRef = React.useRef<Animated.CompositeAnimation | null>(null);
|
||||||
|
const cancelButtonAnimation = React.useRef(new Animated.Value(0)).current;
|
||||||
|
|
||||||
const animation = React.useCallback(() => {
|
const animation = React.useCallback(() => {
|
||||||
if (isRunning || containerHeight === 0) {
|
if (isRunning || containerHeight === 0) {
|
||||||
@@ -59,74 +62,99 @@ export default function App() {
|
|||||||
const totalSeconds = duration * TIMER_UNIT_IN_SECONDS;
|
const totalSeconds = duration * TIMER_UNIT_IN_SECONDS;
|
||||||
setTimeRemaining(totalSeconds);
|
setTimeRemaining(totalSeconds);
|
||||||
|
|
||||||
const countdown = setInterval(() => {
|
if (countdownRef.current) {
|
||||||
|
clearInterval(countdownRef.current);
|
||||||
|
countdownRef.current = null;
|
||||||
|
}
|
||||||
|
countdownRef.current = setInterval(() => {
|
||||||
setTimeRemaining((currentTime) => {
|
setTimeRemaining((currentTime) => {
|
||||||
if (currentTime <= 1) {
|
if (currentTime <= 1) {
|
||||||
clearInterval(countdown)
|
if (countdownRef.current) {
|
||||||
|
clearInterval(countdownRef.current);
|
||||||
|
countdownRef.current = null;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return currentTime -1;
|
return currentTime -1;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
Animated.sequence([
|
const runningAnimation = Animated.sequence([
|
||||||
Animated.parallel([
|
Animated.parallel([
|
||||||
Animated.timing(buttonAnimation, {
|
Animated.timing(buttonAnimation, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
duration: 300,
|
duration: 300,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}),
|
|
||||||
Animated.timing(taskDetailsAnimation, {
|
|
||||||
toValue: 1,
|
|
||||||
duration: 500,
|
|
||||||
useNativeDriver: true
|
|
||||||
}),
|
|
||||||
Animated.timing(countdownAnimation, {
|
|
||||||
toValue: 1,
|
|
||||||
duration: 300,
|
|
||||||
useNativeDriver: true
|
|
||||||
}),
|
}),
|
||||||
]),
|
Animated.timing(cancelButtonAnimation, {
|
||||||
Animated.timing(timerAnimation, {
|
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,
|
toValue: 0,
|
||||||
duration: 300,
|
duration: 300,
|
||||||
useNativeDriver: true
|
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, {
|
Animated.timing(taskDetailsAnimation, {
|
||||||
toValue: 0,
|
toValue: 0,
|
||||||
duration: 300,
|
duration: 300,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
|
}),
|
||||||
|
Animated.timing(cancelButtonAnimation, {
|
||||||
|
toValue: 0,
|
||||||
|
duration: 300,
|
||||||
|
useNativeDriver: true
|
||||||
}),
|
}),
|
||||||
]).start(() => {
|
]).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(() => {
|
React.useEffect(() => {
|
||||||
if (containerHeight > 0 && !isRunning) {
|
if (containerHeight > 0 && !isRunning) {
|
||||||
@@ -134,27 +162,77 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
}, [containerHeight, isRunning, timerAnimation])
|
}, [containerHeight, isRunning, timerAnimation])
|
||||||
|
|
||||||
const opacity = buttonAnimation.interpolate({
|
const cancelButtonOpacity = cancelButtonAnimation;
|
||||||
|
|
||||||
|
const cancelButtonTranslateY = cancelButtonAnimation.interpolate({
|
||||||
inputRange: [0, 1],
|
inputRange: [0, 1],
|
||||||
outputRange: [1, 0]
|
outputRange: [16, 0],
|
||||||
})
|
})
|
||||||
const translateY = buttonAnimation.interpolate({
|
|
||||||
inputRange: [0, 1],
|
const opacity = buttonAnimation.interpolate({
|
||||||
outputRange: [0, 200]
|
inputRange: [0, 1],
|
||||||
})
|
outputRange: [1, 0]
|
||||||
const inactiveTimerNumberOpacity = buttonAnimation.interpolate({
|
})
|
||||||
inputRange: [0, 1],
|
const translateY = buttonAnimation.interpolate({
|
||||||
outputRange: [1, 0]
|
inputRange: [0, 1],
|
||||||
})
|
outputRange: [0, 200]
|
||||||
const taskDetailsOpacity = taskDetailsAnimation.interpolate({
|
})
|
||||||
inputRange: [0, 1],
|
const inactiveTimerNumberOpacity = buttonAnimation.interpolate({
|
||||||
outputRange: [0, 1]
|
inputRange: [0, 1],
|
||||||
})
|
outputRange: [1, 0]
|
||||||
const taskDetailsTranslateY = taskDetailsAnimation.interpolate({
|
})
|
||||||
inputRange: [0, 1],
|
const taskDetailsOpacity = taskDetailsAnimation.interpolate({
|
||||||
outputRange: [20, 0]
|
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 (
|
return (
|
||||||
<View style={styles.container}
|
<View style={styles.container}
|
||||||
onLayout={(event) => {
|
onLayout={(event) => {
|
||||||
@@ -184,12 +262,28 @@ return (
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
disabled={isRunning}
|
disabled={isRunning}
|
||||||
onPress={animation}>
|
onPress={animation}>
|
||||||
<View
|
<View style={styles.roundButton}>
|
||||||
style={styles.roundButton}
|
<Text className='text-text-main'>Start</Text>
|
||||||
/>
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Animated.View>
|
||||||
|
<Animated.View
|
||||||
|
pointerEvents={isRunning? 'auto' : 'none'}
|
||||||
|
style={[
|
||||||
|
styles.cancelButtonContainer,
|
||||||
|
{
|
||||||
|
opacity: cancelButtonOpacity,
|
||||||
|
transform: [{translateY: cancelButtonTranslateY}],
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<TouchableOpacity onPress={cancelTimer} activeOpacity={0.75}>
|
||||||
|
<View style={styles.cancelButton}>
|
||||||
|
<Text className='text-text-main'>Cancel</Text>
|
||||||
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
<View
|
<View
|
||||||
@@ -292,8 +386,10 @@ const styles = StyleSheet.create({
|
|||||||
height: 80,
|
height: 80,
|
||||||
borderRadius: 80,
|
borderRadius: 80,
|
||||||
backgroundColor: colors.red,
|
backgroundColor: colors.red,
|
||||||
textAlign: 'center',
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
|
|
||||||
text: {
|
text: {
|
||||||
fontSize: ITEM_SIZE * 0.8,
|
fontSize: ITEM_SIZE * 0.8,
|
||||||
fontFamily: 'Menlo',
|
fontFamily: 'Menlo',
|
||||||
@@ -325,5 +421,24 @@ const styles = StyleSheet.create({
|
|||||||
fontFamily: 'Menlo',
|
fontFamily: 'Menlo',
|
||||||
color: colors.text,
|
color: colors.text,
|
||||||
fontWeight: '900',
|
fontWeight: '900',
|
||||||
}
|
},
|
||||||
|
cancelButtonContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 44,
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: 2,
|
||||||
|
},
|
||||||
|
cancelButton: {
|
||||||
|
minWidth: 112,
|
||||||
|
height: 44,
|
||||||
|
borderRadius: 22,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(155, 155, 155, 0.35',
|
||||||
|
backgroundColor: '#beb9a7',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: 22,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user