mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
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.
This commit is contained in:
@@ -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<void> {
|
||||
});
|
||||
}
|
||||
|
||||
// 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() ? <ToastHost /> : null;
|
||||
}
|
||||
|
||||
function withToastHost({ children }: { children: React.ReactElement }) {
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<FocusedToastHost />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function LoadingScreen({ message }: { message: string }) {
|
||||
const c = useColors();
|
||||
return (
|
||||
@@ -175,7 +195,6 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParam
|
||||
<UpdateBanner />
|
||||
<OfflineCacheBanner />
|
||||
<PushOnboardingPrompt />
|
||||
<ToastHost />
|
||||
<AppIconBadge />
|
||||
<Tab.Navigator
|
||||
screenOptions={{
|
||||
@@ -706,7 +725,7 @@ export default function App() {
|
||||
<StatusBar style={statusBarStyle} />
|
||||
{/* On Fabric, native-stack keeps the screen right below the top live and
|
||||
freezes the ones further down. */}
|
||||
<Stack.Navigator screenOptions={{ headerShown: false, freezeOnBlur: true }}>
|
||||
<Stack.Navigator screenOptions={{ headerShown: false, freezeOnBlur: true }} screenLayout={withToastHost}>
|
||||
<Stack.Screen name="MainTabs" component={MainTabsNavigator} />
|
||||
<Stack.Screen name="EmailThread" component={EmailThreadScreen} />
|
||||
<Stack.Screen name="EmailSource" component={EmailSourceScreen} />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
/>
|
||||
|
||||
<AddressActionSheet address={addressSheet} onClose={() => setAddressSheet(null)} />
|
||||
|
||||
{/* The list's host sits under this screen; failures in here need their own. */}
|
||||
<ToastHost />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user