about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/alf/atoms.ts3
-rw-r--r--src/view/com/util/Toast.tsx144
2 files changed, 78 insertions, 69 deletions
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index 1dc2dfa7b..650997c91 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 5462505e1..d510eed87 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(<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()
+
+  // 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)
 
-  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()
-  })
+  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,
-  },
-})