Native toast rework (#4808)
* rework toast to use reanimated * fix animation on iOS --------- Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com>zio/stable
parent
27d712290a
commit
abb709d209
|
@ -145,6 +145,9 @@ export const atoms = {
|
|||
flex_shrink: {
|
||||
flexShrink: 1,
|
||||
},
|
||||
flex_shrink_0: {
|
||||
flexShrink: 0,
|
||||
},
|
||||
justify_start: {
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
|
|
|
@ -1,87 +1,93 @@
|
|||
import React, {useEffect, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import Animated, {FadeInUp, FadeOutUp} from 'react-native-reanimated'
|
||||
import RootSiblings from 'react-native-root-siblings'
|
||||
import React from 'react'
|
||||
import {Animated, StyleSheet, View} from 'react-native'
|
||||
import {Props as FontAwesomeProps} from '@fortawesome/react-native-fontawesome'
|
||||
import {Text} from './text/Text'
|
||||
import {colors} from 'lib/styles'
|
||||
import {useTheme} from 'lib/ThemeContext'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
Props as FontAwesomeProps,
|
||||
} from '@fortawesome/react-native-fontawesome'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {IS_TEST} from '#/env'
|
||||
|
||||
const TIMEOUT = 4e3
|
||||
const TIMEOUT = 3.7e3
|
||||
|
||||
export function show(
|
||||
message: string,
|
||||
_icon: FontAwesomeProps['icon'] = 'check',
|
||||
icon: FontAwesomeProps['icon'] = 'check',
|
||||
) {
|
||||
if (IS_TEST) return
|
||||
const item = new RootSiblings(<Toast message={message} />)
|
||||
const item = new RootSiblings(<Toast message={message} icon={icon} />)
|
||||
// timeout has some leeway to account for the animation
|
||||
setTimeout(() => {
|
||||
item.destroy()
|
||||
}, TIMEOUT)
|
||||
}, TIMEOUT + 1e3)
|
||||
}
|
||||
|
||||
function Toast({message}: {message: string}) {
|
||||
const theme = useTheme()
|
||||
const pal = usePalette('default')
|
||||
const interp = useAnimatedValue(0)
|
||||
function Toast({
|
||||
message,
|
||||
icon,
|
||||
}: {
|
||||
message: string
|
||||
icon: FontAwesomeProps['icon']
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {top} = useSafeAreaInsets()
|
||||
|
||||
React.useEffect(() => {
|
||||
Animated.sequence([
|
||||
Animated.timing(interp, {
|
||||
toValue: 1,
|
||||
duration: 150,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.delay(3700),
|
||||
Animated.timing(interp, {
|
||||
toValue: 0,
|
||||
duration: 150,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]).start()
|
||||
})
|
||||
// for the exit animation to work on iOS the animated component
|
||||
// must not be the root component
|
||||
// so we need to wrap it in a view and unmount the toast ahead of time
|
||||
const [alive, setAlive] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setAlive(false)
|
||||
}, TIMEOUT)
|
||||
}, [])
|
||||
|
||||
const opacityStyle = {opacity: interp}
|
||||
return (
|
||||
<View style={styles.container} pointerEvents="none">
|
||||
<Animated.View
|
||||
style={[
|
||||
pal.view,
|
||||
pal.border,
|
||||
styles.toast,
|
||||
theme.colorScheme === 'dark' && styles.toastDark,
|
||||
opacityStyle,
|
||||
]}>
|
||||
<Text type="lg-medium" style={pal.text}>
|
||||
{message}
|
||||
</Text>
|
||||
</Animated.View>
|
||||
<View
|
||||
style={[a.absolute, {top: top + 15, left: 16, right: 16}]}
|
||||
pointerEvents="none">
|
||||
{alive && (
|
||||
<Animated.View
|
||||
entering={FadeInUp}
|
||||
exiting={FadeOutUp}
|
||||
style={[
|
||||
a.flex_1,
|
||||
t.atoms.bg,
|
||||
a.shadow_lg,
|
||||
a.rounded_sm,
|
||||
t.atoms.border_contrast_medium,
|
||||
a.rounded_sm,
|
||||
a.px_md,
|
||||
a.py_lg,
|
||||
a.border,
|
||||
a.flex_row,
|
||||
a.gap_md,
|
||||
]}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_shrink_0,
|
||||
a.rounded_full,
|
||||
{width: 32, height: 32},
|
||||
t.atoms.bg_contrast_25,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
]}>
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
size={16}
|
||||
style={t.atoms.text_contrast_low}
|
||||
/>
|
||||
</View>
|
||||
<View style={[a.h_full, a.justify_center, a.flex_1]}>
|
||||
<Text style={a.text_md}>{message}</Text>
|
||||
</View>
|
||||
</Animated.View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
position: 'absolute',
|
||||
top: 60,
|
||||
left: 0,
|
||||
right: 0,
|
||||
alignItems: 'center',
|
||||
},
|
||||
toast: {
|
||||
paddingHorizontal: 18,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 24,
|
||||
borderWidth: 1,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.1,
|
||||
shadowOffset: {width: 0, height: 4},
|
||||
marginHorizontal: 6,
|
||||
},
|
||||
toastDark: {
|
||||
backgroundColor: colors.gray6,
|
||||
shadowOpacity: 0.5,
|
||||
},
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue