From abb709d209946cdb0be0a88a6982116f028e0672 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 23 Jul 2024 19:50:47 +0100 Subject: [PATCH] Native toast rework (#4808) * rework toast to use reanimated * fix animation on iOS --------- Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com> --- src/alf/atoms.ts | 3 + src/view/com/util/Toast.tsx | 144 +++++++++++++++++++----------------- 2 files changed, 78 insertions(+), 69 deletions(-) diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index 1dc2dfa7..650997c9 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -145,6 +145,9 @@ export const atoms = { flex_shrink: { flexShrink: 1, }, + flex_shrink_0: { + flexShrink: 0, + }, justify_start: { justifyContent: 'flex-start', }, diff --git a/src/view/com/util/Toast.tsx b/src/view/com/util/Toast.tsx index 5462505e..d510eed8 100644 --- a/src/view/com/util/Toast.tsx +++ b/src/view/com/util/Toast.tsx @@ -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() + const item = new RootSiblings() + // 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 ( - - - - {message} - - + + {alive && ( + + + + + + {message} + + + )} ) } - -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, - }, -})