diff --git a/src/components/PushOnboardingPrompt.tsx b/src/components/PushOnboardingPrompt.tsx index 50c24984..c7bf6e1c 100644 --- a/src/components/PushOnboardingPrompt.tsx +++ b/src/components/PushOnboardingPrompt.tsx @@ -1,9 +1,11 @@ import React from 'react'; -import { View, Text, StyleSheet, Pressable } from 'react-native'; +import { View, Text, StyleSheet, Pressable, Animated, Easing } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { BellRing, X } from 'lucide-react-native'; import Button from './Button'; import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; import { useColors } from '../theme/colors'; +import { useAnimDuration } from '../theme/dynamic'; import { useAuthStore } from '../stores/auth-store'; import { useLocaleStore } from '../stores/locale-store'; import { useSettingsStore } from '../stores/settings-store'; @@ -22,12 +24,18 @@ import { // Mirrors the webmail's push-notification-prompt: a short delay after login // so the inbox renders first, and never again for an account once dismissed. const PROMPT_DELAY_MS = 1500; +// Clears the tab bar, like the toasts (ToastHost). +const TAB_BAR_CLEARANCE = 64; /** * Once-per-account invitation to turn on background notifications. Shown * only where push can actually work (Android with Play services), when the * user has not disabled mail notifications, and until either "Enable" or * "Not now" has been tapped for the active account. + * + * It floats above the tab bar and slides in, like the webmail's card, rather + * than taking space at the top: arriving 1.5 s into a launch it used to push + * the whole mail list down, and it drew under the status bar. */ export function PushOnboardingPrompt(): React.ReactElement | null { const c = useColors(); @@ -42,6 +50,9 @@ export function PushOnboardingPrompt(): React.ReactElement | null { const [visible, setVisible] = React.useState(false); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(null); + const insets = useSafeAreaInsets(); + const duration = useAnimDuration(200); + const appear = React.useRef(new Animated.Value(0)).current; React.useEffect(() => { setVisible(false); @@ -63,6 +74,17 @@ export function PushOnboardingPrompt(): React.ReactElement | null { }; }, [isAuthenticated, client, activeAccountId, emailNotificationsEnabled]); + React.useEffect(() => { + if (!visible) return; + appear.setValue(0); + Animated.timing(appear, { + toValue: 1, + duration, + easing: Easing.out(Easing.cubic), + useNativeDriver: true, + }).start(); + }, [visible, appear, duration]); + if (!visible || !activeAccountId) return null; const handleDismiss = () => { @@ -90,56 +112,77 @@ export function PushOnboardingPrompt(): React.ReactElement | null { }; return ( - - - - - - - {t('push_prompt.title', 'Get notified about new mail')} - - - {t( - 'push_prompt.body', - 'Turn on background notifications so new messages reach you while the app is closed.', - )} - - {error && {error}} - - - + + + + + + + {t('push_prompt.title', 'Get notified about new mail')} + + + {t( + 'push_prompt.body', + 'Turn on background notifications so new messages reach you while the app is closed.', + )} + + {error && {error}} + + + + + + + + - - - - + ); } function makeStyles(c: ThemePalette) { return StyleSheet.create({ + wrap: { + position: 'absolute', + left: spacing.md, + right: spacing.md, + zIndex: 50, + }, card: { flexDirection: 'row', alignItems: 'flex-start', gap: spacing.md, - marginHorizontal: spacing.md, - marginTop: spacing.sm, padding: spacing.md, borderRadius: radius.md, borderWidth: 1, borderColor: c.border, backgroundColor: c.card, + shadowColor: '#000', + shadowOpacity: 0.25, + shadowRadius: 10, + shadowOffset: { width: 0, height: 4 }, + elevation: 6, }, iconWrap: { width: 36,