[fix](client): apply the window insets so nothing draws under the system bars

React Native has Flutter's SafeArea under another name: react-native-safe-area-
context. The app mounted SafeAreaProvider but never consumed a single inset, so
on Android 15+ — where targetSdk 36 forces edge-to-edge and the window extends
behind both system bars — the Home header's settings gear sat underneath the
notification bar and could not be tapped.

A single SafeAreaFrame pads the tree by all four insets once, above the
navigator, so every present and future screen inherits the safe area instead of
each one re-deriving it.
This commit is contained in:
2026-10-19 16:03:17 +01:00
parent b32df04ab9
commit 020a90f0d3
+31 -2
View File
@@ -1,8 +1,8 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import { StatusBar } from 'react-native'; import { StatusBar, View } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { HeroUINativeProviderRaw } from 'heroui-native/provider-raw'; import { HeroUINativeProviderRaw } from 'heroui-native/provider-raw';
import { SafeAreaProvider } from 'react-native-safe-area-context'; import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
import { DarkTheme, NavigationContainer, useNavigation } from '@react-navigation/native'; import { DarkTheme, NavigationContainer, useNavigation } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
@@ -149,12 +149,40 @@ function Root() {
); );
} }
/**
* React Native's equivalent of Flutter's `SafeArea`: pad the whole tree by the
* platform's window insets once, so no screen can draw its header under the
* status bar or its buttons under the navigation bar.
*
* This is not optional on Android 15+: with `targetSdk 36` the system enforces
* edge-to-edge, so the window extends behind both system bars and the app is
* responsible for the padding. Without it the Home header's settings gear sat
* underneath the notification bar and could not be tapped.
*/
function SafeAreaFrame({ children }: { children: React.ReactNode }) {
const insets = useSafeAreaInsets();
return (
<View
style={{
flex: 1,
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right,
}}
>
{children}
</View>
);
}
function App() { function App() {
return ( return (
<GestureHandlerRootView style={{ flex: 1, backgroundColor: bg }}> <GestureHandlerRootView style={{ flex: 1, backgroundColor: bg }}>
<StatusBar barStyle="light-content" backgroundColor={bg} /> <StatusBar barStyle="light-content" backgroundColor={bg} />
<HeroUINativeProviderRaw> <HeroUINativeProviderRaw>
<SafeAreaProvider> <SafeAreaProvider>
<SafeAreaFrame>
<ThemeProvider> <ThemeProvider>
<ConnectionProvider> <ConnectionProvider>
<AuthProvider> <AuthProvider>
@@ -164,6 +192,7 @@ function App() {
</AuthProvider> </AuthProvider>
</ConnectionProvider> </ConnectionProvider>
</ThemeProvider> </ThemeProvider>
</SafeAreaFrame>
</SafeAreaProvider> </SafeAreaProvider>
</HeroUINativeProviderRaw> </HeroUINativeProviderRaw>
</GestureHandlerRootView> </GestureHandlerRootView>