Fixed bug where timer would count space below nav bar as 'usable' space
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
|||||||
Dimensions,
|
Dimensions,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
@@ -18,6 +19,10 @@ const colors = {
|
|||||||
const timers = [...Array(13).keys()].map((i) => (i === 0 ? 1 : i * 5));
|
const timers = [...Array(13).keys()].map((i) => (i === 0 ? 1 : i * 5));
|
||||||
const ITEM_SIZE = width * 0.38;
|
const ITEM_SIZE = width * 0.38;
|
||||||
const ITEM_SPACING = (width - ITEM_SIZE) / 2;
|
const ITEM_SPACING = (width - ITEM_SIZE) / 2;
|
||||||
|
const placeholderTask = {
|
||||||
|
name: 'Read chapter 4',
|
||||||
|
description: 'Focus on the summary questions and write down anything unclear.',
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Har bare skrevet timeren som en egen tab til å begynne med.
|
Har bare skrevet timeren som en egen tab til å begynne med.
|
||||||
@@ -26,136 +31,212 @@ som viser TaskName og Description der tallene står nå
|
|||||||
Kanskje en animert figur hvis vi får tid
|
Kanskje en animert figur hvis vi får tid
|
||||||
*/
|
*/
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const scrollX = React.useRef(new Animated.Value(0)).current;
|
const [containerHeight, setContainerHeight] = React.useState(0)
|
||||||
const [duration, setDuration] = React.useState(timers[0])
|
const scrollX = React.useRef(new Animated.Value(0)).current;
|
||||||
const timerAnimation = React.useRef(new Animated.Value(height)).current
|
const [duration, setDuration] = React.useState(timers[0])
|
||||||
const buttonAnimation = React.useRef(new Animated.Value(0)).current
|
const [isRunning, setIsRunning] = React.useState(false)
|
||||||
const animation = React.useCallback(() => {
|
const timerAnimation = React.useRef(new Animated.Value(0)).current
|
||||||
Animated.sequence([
|
const buttonAnimation = React.useRef(new Animated.Value(0)).current
|
||||||
|
const taskDetailsAnimation = React.useRef(new Animated.Value(0)).current
|
||||||
|
const animation = React.useCallback(() => {
|
||||||
|
if (isRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsRunning(true);
|
||||||
|
taskDetailsAnimation.setValue(0);
|
||||||
|
|
||||||
|
Animated.sequence([
|
||||||
|
Animated.parallel([
|
||||||
Animated.timing(buttonAnimation, {
|
Animated.timing(buttonAnimation, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
duration: 300,
|
duration: 300,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}),
|
}),
|
||||||
Animated.timing(timerAnimation, {
|
Animated.timing(taskDetailsAnimation, {
|
||||||
toValue: 0,
|
toValue: 1,
|
||||||
duration: 300,
|
duration: 500,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}),
|
}),
|
||||||
Animated.timing(timerAnimation, {
|
]),
|
||||||
toValue: height,
|
Animated.timing(timerAnimation, {
|
||||||
duration: duration * 1000,
|
toValue: 0,
|
||||||
useNativeDriver: true
|
duration: 300,
|
||||||
}),
|
useNativeDriver: true
|
||||||
]) .start(() => {
|
}),
|
||||||
|
Animated.timing(timerAnimation, {
|
||||||
|
toValue: containerHeight,
|
||||||
|
duration: duration * 1000,
|
||||||
|
useNativeDriver: true
|
||||||
|
}),
|
||||||
|
]) .start(({ finished }) => {
|
||||||
|
if (!finished) {
|
||||||
|
setIsRunning(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Animated.parallel([
|
||||||
Animated.timing(buttonAnimation, {
|
Animated.timing(buttonAnimation, {
|
||||||
toValue: 0,
|
toValue: 0,
|
||||||
duration: 300,
|
duration: 300,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}).start()
|
}),
|
||||||
|
Animated.timing(taskDetailsAnimation, {
|
||||||
|
toValue: 0,
|
||||||
|
duration: 300,
|
||||||
|
useNativeDriver: true
|
||||||
|
}),
|
||||||
|
]).start(() => {
|
||||||
|
setIsRunning(false);
|
||||||
})
|
})
|
||||||
}, [duration])
|
|
||||||
|
|
||||||
const opacity = buttonAnimation.interpolate({
|
|
||||||
inputRange: [0, 1],
|
|
||||||
outputRange: [1, 0]
|
|
||||||
})
|
})
|
||||||
const translateY = buttonAnimation.interpolate({
|
}, [buttonAnimation, duration, isRunning, taskDetailsAnimation, timerAnimation])
|
||||||
inputRange: [0, 1],
|
|
||||||
outputRange: [0, 200]
|
React.useEffect(() => {
|
||||||
|
if (containerHeight > 0) {
|
||||||
|
timerAnimation.setValue(containerHeight);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<StatusBar hidden />
|
|
||||||
<Animated.View
|
|
||||||
style={[StyleSheet.absoluteFillObject, {
|
|
||||||
height,
|
|
||||||
width,
|
|
||||||
backgroundColor: colors.red,
|
|
||||||
transform: [{
|
|
||||||
translateY: timerAnimation
|
|
||||||
}]
|
|
||||||
}]}
|
|
||||||
/>
|
|
||||||
<Animated.View
|
|
||||||
style={[
|
|
||||||
StyleSheet.absoluteFillObject,
|
|
||||||
{
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingBottom: 100,
|
|
||||||
opacity,
|
|
||||||
transform: [{
|
|
||||||
translateY
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={animation}>
|
|
||||||
<View
|
|
||||||
style={styles.roundButton}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</Animated.View>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: height / 3,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
flex: 1,
|
|
||||||
}}>
|
|
||||||
<Animated.FlatList
|
|
||||||
data={timers}
|
|
||||||
keyExtractor={item => item.toString()}
|
|
||||||
horizontal
|
|
||||||
bounces={false}
|
|
||||||
onScroll={Animated.event(
|
|
||||||
[{nativeEvent: {contentOffset: {x: scrollX}}}],
|
|
||||||
{ useNativeDriver: true}
|
|
||||||
)}
|
|
||||||
showsHorizontalScrollIndicator={false}
|
|
||||||
onMomentumScrollEnd={ev => {
|
|
||||||
const index = Math.round(ev.nativeEvent.contentOffset.x / ITEM_SIZE)
|
|
||||||
setDuration(timers[index]);
|
|
||||||
}}
|
|
||||||
snapToInterval={ITEM_SIZE}
|
|
||||||
decelerationRate={"fast"}
|
|
||||||
style={{flexGrow: 0}}
|
|
||||||
contentContainerStyle={{
|
|
||||||
paddingHorizontal: ITEM_SPACING
|
|
||||||
}}
|
|
||||||
renderItem={({item, index}) => {
|
|
||||||
const inputRange = [
|
|
||||||
(index - 1) * ITEM_SIZE,
|
|
||||||
index * ITEM_SIZE,
|
|
||||||
(index + 1) * ITEM_SIZE,
|
|
||||||
]
|
|
||||||
|
|
||||||
const opacity = scrollX.interpolate({
|
const opacity = buttonAnimation.interpolate({
|
||||||
inputRange,
|
inputRange: [0, 1],
|
||||||
outputRange: [.4, 1, .4]
|
outputRange: [1, 0]
|
||||||
})
|
})
|
||||||
const scale = scrollX.interpolate({
|
const translateY = buttonAnimation.interpolate({
|
||||||
inputRange,
|
inputRange: [0, 1],
|
||||||
outputRange: [.7, 1, .7]
|
outputRange: [0, 200]
|
||||||
})
|
})
|
||||||
return <View style={{width: ITEM_SIZE, justifyContent: 'center', alignItems: 'center'}}>
|
const inactiveTimerNumberOpacity = buttonAnimation.interpolate({
|
||||||
<Animated.Text style={[styles.text, {
|
inputRange: [0, 1],
|
||||||
opacity,
|
outputRange: [1, 0]
|
||||||
transform: [{
|
})
|
||||||
scale
|
const taskDetailsOpacity = taskDetailsAnimation.interpolate({
|
||||||
}]
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0, 1]
|
||||||
|
})
|
||||||
|
const taskDetailsTranslateY = taskDetailsAnimation.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [20, 0]
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}
|
||||||
|
onLayout={(event) => {
|
||||||
|
setContainerHeight(event.nativeEvent.layout.height);
|
||||||
|
}}>
|
||||||
|
<StatusBar hidden />
|
||||||
|
<Animated.View
|
||||||
|
style={[StyleSheet.absoluteFillObject, {
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
backgroundColor: colors.red,
|
||||||
|
transform: [{
|
||||||
|
translateY: timerAnimation
|
||||||
|
}]
|
||||||
|
}]}
|
||||||
|
/>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
StyleSheet.absoluteFillObject,
|
||||||
|
{
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingBottom: 100,
|
||||||
|
opacity,
|
||||||
|
transform: [{
|
||||||
|
translateY
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<TouchableOpacity
|
||||||
|
disabled={isRunning}
|
||||||
|
onPress={animation}>
|
||||||
|
<View
|
||||||
|
style={styles.roundButton}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Animated.View>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: containerHeight / 3,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
flex: 1,
|
||||||
|
}}>
|
||||||
|
<Animated.FlatList
|
||||||
|
data={timers}
|
||||||
|
keyExtractor={item => item.toString()}
|
||||||
|
horizontal
|
||||||
|
bounces={false}
|
||||||
|
onScroll={Animated.event(
|
||||||
|
[{nativeEvent: {contentOffset: {x: scrollX}}}],
|
||||||
|
{ useNativeDriver: true}
|
||||||
|
)}
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
onMomentumScrollEnd={ev => {
|
||||||
|
const index = Math.round(ev.nativeEvent.contentOffset.x / ITEM_SIZE)
|
||||||
|
setDuration(timers[index]);
|
||||||
|
}}
|
||||||
|
snapToInterval={ITEM_SIZE}
|
||||||
|
decelerationRate={"fast"}
|
||||||
|
style={{flexGrow: 0}}
|
||||||
|
contentContainerStyle={{
|
||||||
|
paddingHorizontal: ITEM_SPACING
|
||||||
|
}}
|
||||||
|
renderItem={({item, index}) => {
|
||||||
|
const inputRange = [
|
||||||
|
(index - 1) * ITEM_SIZE,
|
||||||
|
index * ITEM_SIZE,
|
||||||
|
(index + 1) * ITEM_SIZE,
|
||||||
|
]
|
||||||
|
|
||||||
}]}>
|
const normalOpacity = scrollX.interpolate({
|
||||||
{item}
|
inputRange,
|
||||||
</Animated.Text>
|
outputRange: [.4, 1, .4]
|
||||||
</View>
|
})
|
||||||
}
|
const selectedOpacity = scrollX.interpolate({
|
||||||
|
inputRange,
|
||||||
|
outputRange: [0, 1, 0],
|
||||||
|
extrapolate: 'clamp'
|
||||||
|
})
|
||||||
|
const opacity = Animated.add(
|
||||||
|
Animated.multiply(normalOpacity, inactiveTimerNumberOpacity),
|
||||||
|
Animated.multiply(selectedOpacity, buttonAnimation)
|
||||||
|
)
|
||||||
|
const scale = scrollX.interpolate({
|
||||||
|
inputRange,
|
||||||
|
outputRange: [.7, 1, .7]
|
||||||
|
})
|
||||||
|
return <View style={{width: ITEM_SIZE, justifyContent: 'center', alignItems: 'center'}}>
|
||||||
|
<Animated.Text style={[styles.text, {
|
||||||
|
opacity,
|
||||||
|
transform: [{
|
||||||
|
scale
|
||||||
|
}]
|
||||||
|
|
||||||
|
}]}>
|
||||||
|
{item}
|
||||||
|
</Animated.Text>
|
||||||
|
</View>
|
||||||
}
|
}
|
||||||
/>
|
}
|
||||||
</View>
|
/>
|
||||||
|
</View>
|
||||||
|
<Animated.View
|
||||||
|
pointerEvents="none"
|
||||||
|
style={[
|
||||||
|
styles.taskDetails,
|
||||||
|
{
|
||||||
|
opacity: taskDetailsOpacity,
|
||||||
|
transform: [{
|
||||||
|
translateY: taskDetailsTranslateY
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]}>
|
||||||
|
<Text style={styles.taskName}>{placeholderTask.name}</Text>
|
||||||
|
<Text style={styles.taskDescription}>{placeholderTask.description}</Text>
|
||||||
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -176,5 +257,25 @@ const styles = StyleSheet.create({
|
|||||||
fontFamily: 'Menlo',
|
fontFamily: 'Menlo',
|
||||||
color: colors.text,
|
color: colors.text,
|
||||||
fontWeight: '900',
|
fontWeight: '900',
|
||||||
|
},
|
||||||
|
taskDetails: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: height * 0.56,
|
||||||
|
left: 32,
|
||||||
|
right: 32,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
taskName: {
|
||||||
|
color: colors.text,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: '800',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
taskDescription: {
|
||||||
|
color: colors.text,
|
||||||
|
fontSize: 16,
|
||||||
|
lineHeight: 22,
|
||||||
|
marginTop: 10,
|
||||||
|
textAlign: 'center',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user