calintamas / react-native-toast-message

Animated toast message component for React Native
MIT License
1.66k stars 258 forks source link

`topoffset` does not respect Safe Area on iOS (default value no longer appropriate for Dynamic Island, aka iPhone 14 Pro) #409

Open go-sean-go opened 1 year ago

go-sean-go commented 1 year ago

Describe the bug The default value for topOffset is 40, when really it should respect and read from the Safe Area. For reference, the notch on iPhone 13 and earlier was 47pt high, but the SafeArea on the new iPhone 14 Pro with Dynamic Island is 59pt high.

I can't find any part of the library's code that takes Safe Area into consideration, but maybe I'm missing it. No matter the implementation: Toasts now show under or bordering on the physical elements on the iPhone 14 Pro, and may intrude on the Safe Area of other devices by default.

Steps to reproduce Steps to reproduce the behavior:

  1. Show a Toast with custom layout/type on an iPhone 14 Pro (only tested on physical device w/ custom layout, but presumably same for simulator and same without any custom layout/type specified).

Expected behavior Toast message avoids the physical aspects of the phone and displays below the Safe Area. (I tend to use some minimum offset when safeArea.bottom == 0.)

Screenshots Difficult to screenshot because the Dynamic Island/notch are not included in device screenshots.

Code sample Any should work. Please let me know if you cannot repro easily/reliably on an iPhone 14 Pro simulator/device and I can take the time to create one.

Environment (please complete the following information):

Additional context Add any other context about the problem here.

Tucker2015 commented 1 year ago

You can change topOffset default in

/node_modules/react-native-toast-message/lib/src/useToast.js

I have set it at 55.

Chuabacca commented 1 year ago

A better way to do this is to create a custom layout and add at least 10 to the top margin of the various toast components.

Chuabacca commented 1 year ago

This probably isn't the best way, but we could check for the exact dimensions of the iPhone 14 Pro screen and adjust the top offset in useToast.ts like this.

const { height, width } = Dimensions.get("screen")
...
topOffset: (Platform.OS === 'ios' && height === '2556' && width === '1179') ? 55 : 40,
kiran-kumar011 commented 1 year ago

Facing a similar issue in iPhone 14 Pro simulator as well.

Screenshot 2022-10-26 at 6 29 00 PM
Chuabacca commented 1 year ago

@kiran-kumar011 you can add extra spacing to your custom layout for now. See my previous post.

ericledonge commented 1 year ago

On my side, with an iPhone 14 Pro, the display spot is perfect, but I can't see the borders (green or red, depending toast type). Any idea?

rafyc commented 1 year ago

You can use import {useSafeAreaInsets} from 'react-native-safe-area-context';

go-sean-go commented 1 year ago

Just weighing in as OP to clarify my intent with this issue:

For future readers/searchers, certainly the solutions above are reasonable (i.e. custom layout), but it is clearly the intention of the library to account for Safe Areas (hence the default value of topOffset being 40).

I think it would be a very valid solution for the library to be naive to Safe Areas, but in this case the default topOffset should be 0. This way, it will be immediately obvious to consumers of the library that they need to account for it manually, and consumers can add Safe Area support as needed (this could/should also be added to the README, since the solution of using the safe area library is so simple).

Of course, imo, the more reasonable solution is to account for Safe Areas using a common, well-supported community library (see above). This is what react-navigation does, for example - and I would argue a 'toast' library is even more UI-oriented than a navigation library (so thus should make dealing with different idioms easier, not harder).

Either way - discarding Safe Area support completely, or supporting it completely - some decision should be made by a maintainer. The fix either way is very easy.

eruveo commented 1 year ago

You can use import {useSafeAreaInsets} from 'react-native-safe-area-context';

Can you explain how, please?

endyinthesun commented 1 year ago

You can use import {useSafeAreaInsets} from 'react-native-safe-area-context';

Can you explain how, please?

You can use this hook inside your main navigation component. For example:

const App = () => {
  useEffect(() => {
    SplashScreen.hide();
  }, []);

  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <I18nextProvider i18n={i18n}>
          <SafeAreaProvider>
            <BottomSheetModalProvider>
              <StatusBar
                backgroundColor={'transparent'}
                barStyle={'dark-content'}
                translucent
              />
              <Router />
            </BottomSheetModalProvider>
          </SafeAreaProvider>
        </I18nextProvider>
      </PersistGate>
    </Provider>
  );
};
const toastConfig = {
  success: (props: BaseToastProps) => (
    <BaseToast {...props} style={styles.baseToast} />
  ),
  error: (props: BaseToastProps) => (
    <ErrorToast {...props} style={styles.errorToast} />
  ),
};

const Router = () => {
  const {top} = useSafeAreaInsets();
  const isLogin = useAppSelector(state => state.auth.isLogin);
  const initialRouteName = isLogin ? 'Tabs' : 'Auth';

  return (
    <>
      <View style={styles.container}>
        <NavigationContainer ref={navRef} theme={THEME}>
          <RootStack.Navigator
            screenOptions={SCREEN_OPTIONS}
            initialRouteName={initialRouteName}>
            <RootStack.Screen name="Auth" component={AuthStack} />
            <RootStack.Screen name="Tabs" component={TabsStack} />
            <RootStack.Screen name="Order" component={OrderStack} />
            <RootStack.Screen
              name="Modal"
              options={{
                gestureEnabled: true,
                cardShadowEnabled: false,
                cardOverlayEnabled: true,
                gestureResponseDistance: s(150),
                animationTypeForReplace: 'pop',
                ...TransitionPresets.ModalPresentationIOS,
              }}
              component={ModalStack}
            />
          </RootStack.Navigator>
        </NavigationContainer>
      </View>
      <Toast config={toastConfig} topOffset={top} />
    </>
  );
};
BuiHung1612 commented 8 months ago

in my case. When I want to show toast position: 'top' on iPhone with dynamic island, I pass topOffset like this:

import { useSafeAreaInsets } from 'react-native-safe-area-context';

const insets = useSafeAreaInsets();
Toast.show({
                type: 'notification',
                position: 'top',
                text1: title,
                text2: body,
                visibilityTime: MESSAGE_TIMEOUT,
                topOffset: insets.top,

            });