reverting to chase a bug w expo
This commit is contained in:
@@ -23,11 +23,7 @@ const ITEM_SIZE = width * 0.38;
|
|||||||
const ITEM_SPACING = (width - ITEM_SIZE) / 2;
|
const ITEM_SPACING = (width - ITEM_SIZE) / 2;
|
||||||
const TIMER_UNIT_IN_SECONDS = 60; // Set to 60 for timer value to represent minutes
|
const TIMER_UNIT_IN_SECONDS = 60; // Set to 60 for timer value to represent minutes
|
||||||
const HOLD_TO_CANCEL_MS = 2000;
|
const HOLD_TO_CANCEL_MS = 2000;
|
||||||
const CANCEL_ANIMATION_DELAY_MS = 250;
|
const CANCEL_ANIMATION_DELAY_MS = 250
|
||||||
const CANCEL_RELEASE_MS = 750;
|
|
||||||
const START_TRANSITION_MS = 300;
|
|
||||||
const TIMER_RESET_MS = 300;
|
|
||||||
const COUNTDOWN_FADE_MS = 180;
|
|
||||||
const placeholderTask = {
|
const placeholderTask = {
|
||||||
name: 'Read chapter 4',
|
name: 'Read chapter 4',
|
||||||
description: 'Focus on the summary questions and write down anything unclear.',
|
description: 'Focus on the summary questions and write down anything unclear.',
|
||||||
@@ -41,61 +37,31 @@ function formatTime(totalSeconds: number) {
|
|||||||
seconds.toString().padStart(2, '0')}`;
|
seconds.toString().padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCancelOverlayTarget({
|
|
||||||
containerHeight,
|
|
||||||
holdStartedAt,
|
|
||||||
sessionStartedAt,
|
|
||||||
sessionDurationMs,
|
|
||||||
}: {
|
|
||||||
containerHeight: number;
|
|
||||||
holdStartedAt: number;
|
|
||||||
sessionStartedAt: number;
|
|
||||||
sessionDurationMs: number;
|
|
||||||
}) {
|
|
||||||
const now = Date.now();
|
|
||||||
const elapsedHoldMs = now - holdStartedAt;
|
|
||||||
const remainingHoldMs = Math.max(1, HOLD_TO_CANCEL_MS - elapsedHoldMs);
|
|
||||||
const elapsedAtCancelMs = now + remainingHoldMs - sessionStartedAt;
|
|
||||||
const expectedProgress = elapsedAtCancelMs / sessionDurationMs;
|
|
||||||
const clampedProgress = Math.max(0, Math.min(expectedProgress, 1));
|
|
||||||
const expectedYAtCancel = containerHeight * clampedProgress;
|
|
||||||
const cancelOffset = Math.max(0, containerHeight - expectedYAtCancel);
|
|
||||||
|
|
||||||
return { cancelOffset, remainingHoldMs };
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [containerHeight, setContainerHeight] = React.useState(0);
|
const [containerHeight, setContainerHeight] = React.useState(0)
|
||||||
const [duration, setDuration] = React.useState(timers[0]);
|
const [duration, setDuration] = React.useState(timers[0])
|
||||||
const [timerIsRunning, setIsRunning] = React.useState(false);
|
const [timerIsRunning, setIsRunning] = React.useState(false)
|
||||||
const [timeRemaining, setTimeRemaining] = React.useState(0);
|
const [timeRemaining, setTimeRemaining] = React.useState(0)
|
||||||
|
|
||||||
// Animated values
|
|
||||||
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 cancelButtonAnimation = React.useRef(new Animated.Value(0)).current;
|
|
||||||
const pressedButtonAnimation = React.useRef(new Animated.Value(0)).current;
|
|
||||||
const focusModeAnimation = React.useRef(new Animated.Value(0)).current; // 0 = timer inactive, 1 = timer active
|
|
||||||
const timerOverlayOpacity = React.useRef(new Animated.Value(1)).current;
|
|
||||||
const cancelOverlayAnimation = React.useRef(new Animated.Value(0)).current;
|
|
||||||
|
|
||||||
// Timer/session refs
|
|
||||||
const countdownRef = React.useRef<ReturnType<typeof setInterval> | null>(null);
|
const countdownRef = React.useRef<ReturnType<typeof setInterval> | null>(null);
|
||||||
|
const cancelHoldTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const runningAnimationRef = React.useRef<Animated.CompositeAnimation | null>(null);
|
const runningAnimationRef = React.useRef<Animated.CompositeAnimation | null>(null);
|
||||||
const progressAnimationRef = React.useRef<Animated.CompositeAnimation | null>(null);
|
const progressAnimationRef = React.useRef<Animated.CompositeAnimation | null>(null);
|
||||||
const sessionStartedAtRef = React.useRef<number | null>(null);
|
const sessionStartedAtRef = React.useRef<number | null>(null);
|
||||||
const sessionDurationMsRef = React.useRef(0);
|
const sessionDurationMsRef = React.useRef(0);
|
||||||
|
const cancelButtonAnimation = React.useRef(new Animated.Value(0)).current;
|
||||||
// Cancel-hold refs
|
const pressedButtonAnimation = React.useRef(new Animated.Value(0)).current;
|
||||||
const cancelHoldTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
const focusModeAnimation = React.useRef(new Animated.Value(0)).current; // 0 = timer inactive, 1 = timer active
|
||||||
const cancelHoldAnimationDelayRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
const cancelHoldAnimationDelayRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const cancelAccelStartedRef = React.useRef(false);
|
const cancelAccelStartedRef = React.useRef(false);
|
||||||
const cancelHoldActiveRef = React.useRef(false);
|
const cancelHoldActiveRef = React.useRef(false);
|
||||||
const cancelHoldIdRef = React.useRef(0);
|
const cancelHoldIdRef = React.useRef(0);
|
||||||
const cancelHoldStartedAtRef = React.useRef(0);
|
const cancelHoldStartedAtRef = React.useRef(0);
|
||||||
|
const cancelOverlayAnimation = React.useRef(new Animated.Value(0)).current;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (containerHeight > 0 && !timerIsRunning) {
|
if (containerHeight > 0 && !timerIsRunning) {
|
||||||
@@ -103,6 +69,8 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
}, [containerHeight, timerIsRunning, timerAnimation]);
|
}, [containerHeight, timerIsRunning, timerAnimation]);
|
||||||
|
|
||||||
|
const timerOverlayOpacity = React.useRef(new Animated.Value(1)).current;
|
||||||
|
|
||||||
const cancelButtonOpacity = cancelButtonAnimation;
|
const cancelButtonOpacity = cancelButtonAnimation;
|
||||||
|
|
||||||
const pressedButtonScale = pressedButtonAnimation.interpolate({
|
const pressedButtonScale = pressedButtonAnimation.interpolate({
|
||||||
@@ -139,12 +107,12 @@ export default function App() {
|
|||||||
outputRange: [1, 0.55],
|
outputRange: [1, 0.55],
|
||||||
});
|
});
|
||||||
|
|
||||||
const startButtonOpacity = buttonAnimation.interpolate({
|
const opacity = buttonAnimation.interpolate({
|
||||||
inputRange: [0, 1],
|
inputRange: [0, 1],
|
||||||
outputRange: [1, 0]
|
outputRange: [1, 0]
|
||||||
});
|
});
|
||||||
|
|
||||||
const startButtonTranslateY = buttonAnimation.interpolate({
|
const translateY = buttonAnimation.interpolate({
|
||||||
inputRange: [0, 1],
|
inputRange: [0, 1],
|
||||||
outputRange: [0, 200]
|
outputRange: [0, 200]
|
||||||
});
|
});
|
||||||
@@ -164,35 +132,6 @@ export default function App() {
|
|||||||
outputRange: [20, 0]
|
outputRange: [20, 0]
|
||||||
});
|
});
|
||||||
|
|
||||||
const clearCountdown = React.useCallback(() => {
|
|
||||||
if (countdownRef.current) {
|
|
||||||
clearInterval(countdownRef.current);
|
|
||||||
countdownRef.current = null;
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const clearCancelHoldTimers = React.useCallback(() => {
|
|
||||||
if (cancelHoldTimeoutRef.current) {
|
|
||||||
clearTimeout(cancelHoldTimeoutRef.current);
|
|
||||||
cancelHoldTimeoutRef.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cancelHoldAnimationDelayRef.current) {
|
|
||||||
clearTimeout(cancelHoldAnimationDelayRef.current);
|
|
||||||
cancelHoldAnimationDelayRef.current = null;
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const stopTimerAnimations = React.useCallback(() => {
|
|
||||||
runningAnimationRef.current?.stop();
|
|
||||||
runningAnimationRef.current = null;
|
|
||||||
|
|
||||||
progressAnimationRef.current?.stop();
|
|
||||||
progressAnimationRef.current = null;
|
|
||||||
|
|
||||||
cancelOverlayAnimation.stopAnimation();
|
|
||||||
}, [cancelOverlayAnimation]);
|
|
||||||
|
|
||||||
const animateButtonPress = React.useCallback((pressed: boolean) => {
|
const animateButtonPress = React.useCallback((pressed: boolean) => {
|
||||||
Animated.timing(pressedButtonAnimation, {
|
Animated.timing(pressedButtonAnimation, {
|
||||||
toValue: pressed ? 1 : 0,
|
toValue: pressed ? 1 : 0,
|
||||||
@@ -205,8 +144,16 @@ export default function App() {
|
|||||||
if (!timerIsRunning){
|
if (!timerIsRunning){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
clearCountdown();
|
if (countdownRef.current) {
|
||||||
stopTimerAnimations();
|
clearInterval(countdownRef.current);
|
||||||
|
countdownRef.current = null;
|
||||||
|
}
|
||||||
|
runningAnimationRef.current?.stop();
|
||||||
|
runningAnimationRef.current = null;
|
||||||
|
|
||||||
|
progressAnimationRef.current?.stop();
|
||||||
|
progressAnimationRef.current = null;
|
||||||
|
cancelOverlayAnimation.stopAnimation();
|
||||||
|
|
||||||
Animated.parallel([
|
Animated.parallel([
|
||||||
Animated.timing(cancelButtonAnimation, {
|
Animated.timing(cancelButtonAnimation, {
|
||||||
@@ -226,12 +173,12 @@ export default function App() {
|
|||||||
}),
|
}),
|
||||||
Animated.timing(countdownAnimation, {
|
Animated.timing(countdownAnimation, {
|
||||||
toValue: 0,
|
toValue: 0,
|
||||||
duration: COUNTDOWN_FADE_MS,
|
duration: 180,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}),
|
}),
|
||||||
Animated.timing(timerAnimation, {
|
Animated.timing(timerAnimation, {
|
||||||
toValue: containerHeight,
|
toValue: containerHeight,
|
||||||
duration: TIMER_RESET_MS,
|
duration: 300,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}),
|
}),
|
||||||
Animated.timing(timerOverlayOpacity, {
|
Animated.timing(timerOverlayOpacity, {
|
||||||
@@ -251,7 +198,7 @@ export default function App() {
|
|||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
})
|
})
|
||||||
.start(() => {
|
.start(() => {
|
||||||
timerAnimation.setValue(containerHeight);
|
timerAnimation.setValue(containerHeight)
|
||||||
cancelOverlayAnimation.setValue(0);
|
cancelOverlayAnimation.setValue(0);
|
||||||
timerOverlayOpacity.setValue(1);
|
timerOverlayOpacity.setValue(1);
|
||||||
setTimeRemaining(0);
|
setTimeRemaining(0);
|
||||||
@@ -259,9 +206,8 @@ export default function App() {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
}, [timerOverlayOpacity, buttonAnimation, cancelButtonAnimation,
|
}, [timerOverlayOpacity, buttonAnimation, cancelButtonAnimation,
|
||||||
cancelOverlayAnimation, clearCountdown, containerHeight, countdownAnimation,
|
cancelOverlayAnimation, containerHeight, countdownAnimation, timerAnimation,
|
||||||
focusModeAnimation, stopTimerAnimations, taskDetailsAnimation,
|
timerIsRunning, taskDetailsAnimation, focusModeAnimation]);
|
||||||
timerAnimation, timerIsRunning]);
|
|
||||||
|
|
||||||
const startCancelHold = React.useCallback(() => {
|
const startCancelHold = React.useCallback(() => {
|
||||||
animateButtonPress(true);
|
animateButtonPress(true);
|
||||||
@@ -281,12 +227,14 @@ export default function App() {
|
|||||||
cancelAccelStartedRef.current = true;
|
cancelAccelStartedRef.current = true;
|
||||||
cancelOverlayAnimation.setValue(0);
|
cancelOverlayAnimation.setValue(0);
|
||||||
|
|
||||||
const { cancelOffset, remainingHoldMs } = getCancelOverlayTarget({
|
const elapsedHoldMs = Date.now() - cancelHoldStartedAtRef.current;
|
||||||
containerHeight,
|
const remainingHoldMs = Math.max(1, HOLD_TO_CANCEL_MS - elapsedHoldMs);
|
||||||
holdStartedAt: cancelHoldStartedAtRef.current,
|
const sessionStartedAt = sessionStartedAtRef.current ?? Date.now();
|
||||||
sessionStartedAt: sessionStartedAtRef.current ?? Date.now(),
|
const elapsedAtCancelMs = Date.now() + remainingHoldMs - sessionStartedAt;
|
||||||
sessionDurationMs: sessionDurationMsRef.current,
|
const expectedProgress = elapsedAtCancelMs / sessionDurationMsRef.current;
|
||||||
});
|
const clampedProgress = Math.max(0, Math.min(expectedProgress, 1));
|
||||||
|
const expectedYAtCancel = containerHeight * clampedProgress;
|
||||||
|
const cancelOffset = Math.max(0, containerHeight - expectedYAtCancel);
|
||||||
|
|
||||||
Animated.timing(cancelOverlayAnimation, {
|
Animated.timing(cancelOverlayAnimation, {
|
||||||
toValue: cancelOffset,
|
toValue: cancelOffset,
|
||||||
@@ -304,7 +252,7 @@ export default function App() {
|
|||||||
cancelHoldActiveRef.current = false;
|
cancelHoldActiveRef.current = false;
|
||||||
cancelHoldIdRef.current += 1;
|
cancelHoldIdRef.current += 1;
|
||||||
cancelAccelStartedRef.current = false;
|
cancelAccelStartedRef.current = false;
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning)
|
||||||
cancelTimer();
|
cancelTimer();
|
||||||
cancelHoldTimeoutRef.current = null;
|
cancelHoldTimeoutRef.current = null;
|
||||||
}, HOLD_TO_CANCEL_MS);
|
}, HOLD_TO_CANCEL_MS);
|
||||||
@@ -331,12 +279,12 @@ export default function App() {
|
|||||||
Animated.parallel([
|
Animated.parallel([
|
||||||
Animated.timing(buttonAnimation, {
|
Animated.timing(buttonAnimation, {
|
||||||
toValue: 0,
|
toValue: 0,
|
||||||
duration: START_TRANSITION_MS,
|
duration: 300,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}),
|
}),
|
||||||
Animated.timing(cancelButtonAnimation, {
|
Animated.timing(cancelButtonAnimation, {
|
||||||
toValue: 0,
|
toValue: 0,
|
||||||
duration: START_TRANSITION_MS,
|
duration: 300,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}),
|
}),
|
||||||
]).start(() => {
|
]).start(() => {
|
||||||
@@ -380,7 +328,15 @@ export default function App() {
|
|||||||
cancelHoldActiveRef.current = false;
|
cancelHoldActiveRef.current = false;
|
||||||
cancelHoldIdRef.current += 1;
|
cancelHoldIdRef.current += 1;
|
||||||
|
|
||||||
clearCancelHoldTimers();
|
if (cancelHoldTimeoutRef.current) {
|
||||||
|
clearTimeout(cancelHoldTimeoutRef.current);
|
||||||
|
cancelHoldTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancelHoldAnimationDelayRef.current) {
|
||||||
|
clearTimeout(cancelHoldAnimationDelayRef.current);
|
||||||
|
cancelHoldAnimationDelayRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!cancelAccelStartedRef.current) {
|
if (!cancelAccelStartedRef.current) {
|
||||||
return;
|
return;
|
||||||
@@ -391,18 +347,18 @@ export default function App() {
|
|||||||
cancelOverlayAnimation.setValue(currentOffset);
|
cancelOverlayAnimation.setValue(currentOffset);
|
||||||
Animated.timing(cancelOverlayAnimation, {
|
Animated.timing(cancelOverlayAnimation, {
|
||||||
toValue: 0,
|
toValue: 0,
|
||||||
duration: CANCEL_RELEASE_MS,
|
duration: 750,
|
||||||
easing: Easing.in(Easing.bounce),
|
easing: Easing.in(Easing.bounce),
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}).start();
|
}).start();
|
||||||
});
|
});
|
||||||
}, [animateButtonPress, cancelOverlayAnimation, clearCancelHoldTimers]);
|
}, [animateButtonPress, cancelOverlayAnimation]);
|
||||||
|
|
||||||
const startTimer = React.useCallback(() => {
|
const animation = React.useCallback(() => {
|
||||||
if (timerIsRunning || containerHeight === 0) {
|
if (timerIsRunning || containerHeight === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
|
||||||
setIsRunning(true);
|
setIsRunning(true);
|
||||||
taskDetailsAnimation.setValue(0);
|
taskDetailsAnimation.setValue(0);
|
||||||
countdownAnimation.setValue(0);
|
countdownAnimation.setValue(0);
|
||||||
@@ -414,11 +370,17 @@ export default function App() {
|
|||||||
sessionStartedAtRef.current = Date.now();
|
sessionStartedAtRef.current = Date.now();
|
||||||
sessionDurationMsRef.current = totalSeconds * 1000;
|
sessionDurationMsRef.current = totalSeconds * 1000;
|
||||||
|
|
||||||
clearCountdown();
|
if (countdownRef.current) {
|
||||||
|
clearInterval(countdownRef.current);
|
||||||
|
countdownRef.current = null;
|
||||||
|
}
|
||||||
countdownRef.current = setInterval(() => {
|
countdownRef.current = setInterval(() => {
|
||||||
setTimeRemaining((currentTime) => {
|
setTimeRemaining((currentTime) => {
|
||||||
if (currentTime <= 1) {
|
if (currentTime <= 1) {
|
||||||
clearCountdown();
|
if (countdownRef.current) {
|
||||||
|
clearInterval(countdownRef.current);
|
||||||
|
countdownRef.current = null;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return currentTime -1;
|
return currentTime -1;
|
||||||
@@ -429,23 +391,23 @@ export default function App() {
|
|||||||
Animated.parallel([
|
Animated.parallel([
|
||||||
Animated.timing(buttonAnimation, {
|
Animated.timing(buttonAnimation, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
duration: START_TRANSITION_MS,
|
duration: 300,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}),
|
}),
|
||||||
Animated.timing(cancelButtonAnimation, {
|
Animated.timing(cancelButtonAnimation, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
duration: START_TRANSITION_MS,
|
duration: 300,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Animated.timing(countdownAnimation, {
|
Animated.timing(countdownAnimation, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
duration: START_TRANSITION_MS,
|
duration: 300,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}),
|
}),
|
||||||
Animated.timing(timerAnimation, {
|
Animated.timing(timerAnimation, {
|
||||||
toValue: 0,
|
toValue: 0,
|
||||||
duration: START_TRANSITION_MS,
|
duration: 300,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -472,20 +434,24 @@ export default function App() {
|
|||||||
startProgressAnimation(0);
|
startProgressAnimation(0);
|
||||||
});
|
});
|
||||||
}, [cancelButtonAnimation, countdownAnimation,
|
}, [cancelButtonAnimation, countdownAnimation,
|
||||||
buttonAnimation, cancelOverlayAnimation, clearCountdown, taskDetailsAnimation,
|
buttonAnimation, cancelOverlayAnimation, taskDetailsAnimation,
|
||||||
timerAnimation, focusModeAnimation, duration, timerIsRunning, containerHeight, startProgressAnimation]);
|
timerAnimation, focusModeAnimation, duration, timerIsRunning, containerHeight, startProgressAnimation]);
|
||||||
const renderTimerOverlay = () => (
|
return (
|
||||||
|
<View style={styles.container}
|
||||||
|
onLayout={(event) => {
|
||||||
|
setContainerHeight(event.nativeEvent.layout.height);
|
||||||
|
}}>
|
||||||
|
<StatusBar hidden />
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[StyleSheet.absoluteFillObject, {
|
style={[StyleSheet.absoluteFillObject, {
|
||||||
height: containerHeight,
|
height: containerHeight,
|
||||||
width,
|
width,
|
||||||
backgroundColor: colors.red,
|
backgroundColor: colors.red,
|
||||||
transform: [{ translateY: timerOverlayTranslateY }]
|
transform: [{
|
||||||
|
translateY: timerOverlayTranslateY
|
||||||
|
}]
|
||||||
}]}
|
}]}
|
||||||
/>
|
/>
|
||||||
);
|
|
||||||
|
|
||||||
const renderStartButton = () => (
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
StyleSheet.absoluteFillObject,
|
StyleSheet.absoluteFillObject,
|
||||||
@@ -493,17 +459,18 @@ export default function App() {
|
|||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingBottom: 100,
|
paddingBottom: 100,
|
||||||
opacity: startButtonOpacity,
|
opacity,
|
||||||
transform: [{ translateY: startButtonTranslateY }]
|
transform: [{
|
||||||
|
translateY
|
||||||
|
}]
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
disabled={timerIsRunning}
|
disabled={timerIsRunning}
|
||||||
onPress={startTimer}
|
onPress={animation}
|
||||||
onPressIn={() => animateButtonPress(true)}
|
onPressIn={() => animateButtonPress(true)}
|
||||||
onPressOut={() => animateButtonPress(false)}>
|
onPressOut={() => animateButtonPress(false)}>
|
||||||
<Animated.View style={[
|
<Animated.View style={[styles.roundButton,
|
||||||
styles.roundButton,
|
|
||||||
{
|
{
|
||||||
transform: [{ scale: pressedButtonScale }],
|
transform: [{ scale: pressedButtonScale }],
|
||||||
},
|
},
|
||||||
@@ -513,9 +480,6 @@ export default function App() {
|
|||||||
</Animated.View>
|
</Animated.View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
);
|
|
||||||
|
|
||||||
const renderCancelButton = () => (
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
pointerEvents={timerIsRunning? 'auto' : 'none'}
|
pointerEvents={timerIsRunning? 'auto' : 'none'}
|
||||||
style={[
|
style={[
|
||||||
@@ -528,21 +492,18 @@ export default function App() {
|
|||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPressIn={startCancelHold}
|
onPressIn={startCancelHold}
|
||||||
onPressOut={stopCancelHold}>
|
onPressOut={stopCancelHold}>
|
||||||
<Animated.View style={[
|
<Animated.View style={[styles.cancelButton,
|
||||||
styles.cancelButton,
|
|
||||||
{
|
{
|
||||||
transform: [{ scale: pressedButtonScale }],
|
transform: [{ scale: pressedButtonScale }],
|
||||||
},
|
},
|
||||||
]}>
|
]}
|
||||||
|
>
|
||||||
<Text className='text-text-main text-xl'>Hold to end sprint</Text>
|
<Text className='text-text-main text-xl'>Hold to end sprint</Text>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
);
|
|
||||||
|
|
||||||
const renderCountdownOverlay = () => (
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
pointerEvents="none"
|
pointerEvents= 'none'
|
||||||
style={[
|
style={[
|
||||||
styles.countdownOverlay, {
|
styles.countdownOverlay, {
|
||||||
opacity: countdownAnimation,
|
opacity: countdownAnimation,
|
||||||
@@ -552,12 +513,10 @@ export default function App() {
|
|||||||
{scale: countdownScale},
|
{scale: countdownScale},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]}>
|
]}
|
||||||
|
>
|
||||||
<Text style= {styles.countdownText}>{formatTime(timeRemaining)}</Text>
|
<Text style= {styles.countdownText}>{formatTime(timeRemaining)}</Text>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
);
|
|
||||||
|
|
||||||
const renderDurationPicker = () => (
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -566,6 +525,7 @@ export default function App() {
|
|||||||
right: 0,
|
right: 0,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
}}>
|
}}>
|
||||||
|
|
||||||
<Animated.FlatList
|
<Animated.FlatList
|
||||||
data={timers}
|
data={timers}
|
||||||
scrollEnabled={!timerIsRunning}
|
scrollEnabled={!timerIsRunning}
|
||||||
@@ -586,74 +546,58 @@ export default function App() {
|
|||||||
const clampedIndex = Math.max(0, Math.min(index, timers.length - 1));
|
const clampedIndex = Math.max(0, Math.min(index, timers.length - 1));
|
||||||
setDuration(timers[clampedIndex]);
|
setDuration(timers[clampedIndex]);
|
||||||
}}
|
}}
|
||||||
|
|
||||||
snapToInterval={ITEM_SIZE}
|
snapToInterval={ITEM_SIZE}
|
||||||
decelerationRate="fast"
|
decelerationRate={"fast"}
|
||||||
style={{flexGrow: 0}}
|
style={{flexGrow: 0}}
|
||||||
contentContainerStyle={{
|
contentContainerStyle={{
|
||||||
paddingHorizontal: ITEM_SPACING
|
paddingHorizontal: ITEM_SPACING
|
||||||
}}
|
}}
|
||||||
renderItem={({item, index}) => {
|
renderItem={({item, index}) => {
|
||||||
|
const timerText = item;
|
||||||
const inputRange = [
|
const inputRange = [
|
||||||
(index - 1) * ITEM_SIZE,
|
(index - 1) * ITEM_SIZE,
|
||||||
index * ITEM_SIZE,
|
index * ITEM_SIZE,
|
||||||
(index + 1) * ITEM_SIZE,
|
(index + 1) * ITEM_SIZE,
|
||||||
];
|
]
|
||||||
const normalOpacity = scrollX.interpolate({
|
const normalOpacity = scrollX.interpolate({
|
||||||
inputRange,
|
inputRange,
|
||||||
outputRange: [.4, 1, .4]
|
outputRange: [.4, 1, .4]
|
||||||
});
|
})
|
||||||
const timerTextOpacity = Animated.multiply(normalOpacity, inactiveTimerNumberOpacity);
|
const opacity = Animated.multiply(normalOpacity, inactiveTimerNumberOpacity);
|
||||||
const scale = scrollX.interpolate({
|
const scale = scrollX.interpolate({
|
||||||
inputRange,
|
inputRange,
|
||||||
outputRange: [.7, 1, .7]
|
outputRange: [.7, 1, .7]
|
||||||
});
|
})
|
||||||
|
return <View style={{width: ITEM_SIZE, justifyContent: 'center', alignItems: 'center'}}>
|
||||||
|
<Animated.Text style={[styles.text, {
|
||||||
|
opacity,
|
||||||
|
transform: [{
|
||||||
|
scale
|
||||||
|
}]
|
||||||
|
|
||||||
return (
|
}]}>
|
||||||
<View style={styles.timerItem}>
|
{timerText}
|
||||||
<Animated.Text style={[
|
|
||||||
styles.text,
|
|
||||||
{
|
|
||||||
opacity: timerTextOpacity,
|
|
||||||
transform: [{ scale }]
|
|
||||||
}
|
|
||||||
]}>
|
|
||||||
{item}
|
|
||||||
</Animated.Text>
|
</Animated.Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
}
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
|
||||||
|
|
||||||
const renderTaskDetails = () => (
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
style={[
|
style={[
|
||||||
styles.taskDetails,
|
styles.taskDetails,
|
||||||
{
|
{
|
||||||
opacity: taskDetailsOpacity,
|
opacity: taskDetailsOpacity,
|
||||||
transform: [{ translateY: taskDetailsTranslateY }]
|
transform: [{
|
||||||
|
translateY: taskDetailsTranslateY
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
]}>
|
]}>
|
||||||
<Text style={styles.taskName}>{placeholderTask.name}</Text>
|
<Text style={styles.taskName}>{placeholderTask.name}</Text>
|
||||||
<Text style={styles.taskDescription}>{placeholderTask.description}</Text>
|
<Text style={styles.taskDescription}>{placeholderTask.description}</Text>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={styles.container}
|
|
||||||
onLayout={(event) => {
|
|
||||||
setContainerHeight(event.nativeEvent.layout.height);
|
|
||||||
}}>
|
|
||||||
<StatusBar hidden />
|
|
||||||
{renderTimerOverlay()}
|
|
||||||
{renderStartButton()}
|
|
||||||
{renderCancelButton()}
|
|
||||||
{renderCountdownOverlay()}
|
|
||||||
{renderDurationPicker()}
|
|
||||||
{renderTaskDetails()}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -677,11 +621,6 @@ const styles = StyleSheet.create({
|
|||||||
color: colors.text,
|
color: colors.text,
|
||||||
fontWeight: '900',
|
fontWeight: '900',
|
||||||
},
|
},
|
||||||
timerItem: {
|
|
||||||
width: ITEM_SIZE,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
taskDetails: {
|
taskDetails: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: height * 0.34,
|
top: height * 0.34,
|
||||||
|
|||||||
Reference in New Issue
Block a user