tooot/src/components/Timeline/Refresh.tsx

185 lines
4.8 KiB
TypeScript
Raw Normal View History

2021-02-17 21:39:38 +01:00
import haptics from '@components/haptics'
import Icon from '@components/Icon'
2021-02-08 23:47:20 +01:00
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
2021-02-17 21:39:38 +01:00
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
2021-02-08 23:47:20 +01:00
import { Circle } from 'react-native-animated-spinkit'
2021-02-17 21:39:38 +01:00
import Animated, {
Extrapolate,
interpolate,
runOnJS,
useAnimatedReaction,
useAnimatedStyle,
useSharedValue,
withTiming
} from 'react-native-reanimated'
2021-02-08 23:47:20 +01:00
export interface Props {
2021-02-17 21:39:38 +01:00
scrollY: Animated.SharedValue<number>
2021-02-08 23:47:20 +01:00
isLoading: boolean
2021-02-17 21:39:38 +01:00
isFetching: boolean
2021-02-08 23:47:20 +01:00
disable?: boolean
}
2021-02-17 21:39:38 +01:00
const CONTAINER_HEIGHT = StyleConstants.Spacing.M * 2.5
const TimelineRefresh = React.memo(
({ scrollY, isLoading, isFetching, disable = false }: Props) => {
if (disable || isLoading) {
return null
}
const { t } = useTranslation('componentTimeline')
const { theme } = useTheme()
const separation01 = -(
CONTAINER_HEIGHT / 2 +
StyleConstants.Font.Size.S / 2
)
const separation02 = -(
CONTAINER_HEIGHT * 1.5 +
StyleConstants.Font.Size.S / 2
)
const [textRight, setTextRight] = useState(0)
const arrowY = useAnimatedStyle(() => ({
transform: [
{
translateY: interpolate(
scrollY.value,
[0, separation01],
[
-CONTAINER_HEIGHT / 2 - StyleConstants.Font.Size.M / 2,
CONTAINER_HEIGHT / 2 - StyleConstants.Font.Size.S / 2
],
Extrapolate.CLAMP
)
}
]
}))
const arrowTop = useAnimatedStyle(() => ({
marginTop:
scrollY.value < separation02
? withTiming(CONTAINER_HEIGHT)
: withTiming(0)
}))
const arrowStage = useSharedValue(0)
const onLayout = useCallback(
({ nativeEvent }) => {
if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) {
setTextRight(nativeEvent.layout.x + nativeEvent.layout.width)
}
},
[textRight]
)
useAnimatedReaction(
() => {
if (isFetching) {
return false
}
switch (arrowStage.value) {
case 0:
if (scrollY.value < separation01) {
arrowStage.value = 1
return true
}
return false
case 1:
if (scrollY.value < separation02) {
arrowStage.value = 2
return true
}
if (scrollY.value > separation01) {
arrowStage.value = 0
return false
}
return false
case 2:
if (scrollY.value > separation02) {
arrowStage.value = 1
return false
}
return false
}
},
data => {
if (data) {
runOnJS(haptics)('Light')
}
},
[isFetching]
)
return (
<View style={styles.base}>
{isFetching ? (
<View style={styles.container2}>
<Circle size={StyleConstants.Font.Size.L} color={theme.secondary} />
</View>
) : (
<>
<View style={styles.container1}>
<Text
style={[styles.explanation, { color: theme.primary }]}
onLayout={onLayout}
children={t('refresh.fetchPreviousPage')}
/>
<Animated.View
style={[
{
position: 'absolute',
left: textRight + StyleConstants.Spacing.S
},
arrowY,
arrowTop
]}
children={
<Icon
name='ArrowLeft'
size={StyleConstants.Font.Size.M}
color={theme.primary}
/>
}
/>
</View>
<View style={styles.container2}>
<Text
style={[styles.explanation, { color: theme.primary }]}
onLayout={onLayout}
children={t('refresh.refetch')}
/>
</View>
</>
)}
</View>
)
},
(prev, next) =>
prev.isLoading === next.isLoading && prev.isFetching === next.isFetching
)
2021-02-08 23:47:20 +01:00
const styles = StyleSheet.create({
base: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
2021-02-17 21:39:38 +01:00
height: CONTAINER_HEIGHT * 2,
2021-02-08 23:47:20 +01:00
alignItems: 'center'
2021-02-17 21:39:38 +01:00
},
container1: {
flex: 1,
flexDirection: 'row',
height: CONTAINER_HEIGHT
},
container2: { height: CONTAINER_HEIGHT, justifyContent: 'center' },
explanation: {
fontSize: StyleConstants.Font.Size.S,
lineHeight: CONTAINER_HEIGHT
2021-02-08 23:47:20 +01:00
}
})
export default TimelineRefresh