From cd911ada790dcfc01e5302a8f78d64d65262cf52 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 24 Sep 2026 20:03:34 +0200 Subject: [PATCH] fix: show toasts on every screen, including the Unified Inbox and contacts Only the tab screens and the viewer had a toast host, so a viewer action that failed after going back to the Unified Inbox or a contact (both stack screens of their own) raised a toast nobody saw. Every stack screen now gets a host through the navigator's `screenLayout`, and only the focused screen's host renders, so the screen kept live under the top one never shows the same toast a second time. The tab screens' and the viewer's own hosts are gone. --- App.tsx | 25 ++++++++++++++++++++++--- src/components/ToastHost.tsx | 7 ++++--- src/screens/EmailThreadScreen.tsx | 4 ---- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/App.tsx b/App.tsx index 6939ad64..24c78bf4 100644 --- a/App.tsx +++ b/App.tsx @@ -2,7 +2,9 @@ import React from 'react'; import { ActivityIndicator, AppState, Linking, StyleSheet, Text, View } from 'react-native'; import { StatusBar } from 'expo-status-bar'; import { useColorScheme } from 'react-native'; -import { DarkTheme, DefaultTheme, NavigationContainer, createNavigationContainerRef } from '@react-navigation/native'; +import { + DarkTheme, DefaultTheme, NavigationContainer, createNavigationContainerRef, useIsFocused, +} from '@react-navigation/native'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createNativeStackNavigator, type NativeStackScreenProps } from '@react-navigation/native-stack'; import { Mail, Calendar, BookUser, HardDrive, Settings } from 'lucide-react-native'; @@ -147,6 +149,24 @@ async function openDeepLink(link: DeepLink): Promise { }); } +// Toasts show on whichever stack screen is up, so a failure that lands after +// leaving the screen it came from (a viewer action finishing on the Unified +// Inbox or a contact) is still seen. Every screen carries a host, and only +// the focused one renders, so a toast never shows twice (the screen below +// the top one is kept live). +function FocusedToastHost() { + return useIsFocused() ? : null; +} + +function withToastHost({ children }: { children: React.ReactElement }) { + return ( + <> + {children} + + + ); +} + function LoadingScreen({ message }: { message: string }) { const c = useColors(); return ( @@ -175,7 +195,6 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps - {/* On Fabric, native-stack keeps the screen right below the top live and freezes the ones further down. */} - + diff --git a/src/components/ToastHost.tsx b/src/components/ToastHost.tsx index 4fe0aae0..5d5f9088 100644 --- a/src/components/ToastHost.tsx +++ b/src/components/ToastHost.tsx @@ -9,9 +9,10 @@ import { useToastStore, type Toast } from '../stores/toast-store'; import { useLocaleStore } from '../stores/locale-store'; /** - * Renders the toast queue above the tab bar. One host is mounted in App.tsx; - * the email undo snackbar keeps its own component because it is bound to - * the email store's pending-undo entry. + * Renders the toast queue above the tab bar. App.tsx gives every stack screen + * one and only the focused screen's renders; the email undo snackbar keeps + * its own component because it is bound to the email store's pending-undo + * entry. */ export function ToastHost(): React.ReactElement | null { const toasts = useToastStore((s) => s.toasts); diff --git a/src/screens/EmailThreadScreen.tsx b/src/screens/EmailThreadScreen.tsx index e1f1387f..bbb2bfd0 100644 --- a/src/screens/EmailThreadScreen.tsx +++ b/src/screens/EmailThreadScreen.tsx @@ -18,7 +18,6 @@ import { MoveSheet } from '../components/MoveSheet'; import { ThreadMessageCard, ThreadCardPlaceholder } from '../components/email/ThreadMessageCard'; import { QuickReplyBox } from '../components/email/QuickReplyBox'; import { AddressActionSheet } from '../components/email/AddressActionSheet'; -import { ToastHost } from '../components/ToastHost'; import { useEmailStore } from '../stores/email-store'; import { toast } from '../stores/toast-store'; import { @@ -895,9 +894,6 @@ function EmailViewer({ route, navigation }: Props) { /> setAddressSheet(null)} /> - - {/* The list's host sits under this screen; failures in here need their own. */} - ); }