Skip to content

Commit 2a3dd79

Browse files
committed
chore: handle safe area insets better on android (skip e2e)
1 parent a1d6a02 commit 2a3dd79

13 files changed

Lines changed: 152 additions & 11 deletions

File tree

Binary file not shown.

packages/mobile/ios/Podfile.lock

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,72 @@ PODS:
12611261
- ReactCommon/turbomodule/bridging
12621262
- ReactCommon/turbomodule/core
12631263
- Yoga
1264+
- react-native-safe-area-context (5.4.0):
1265+
- DoubleConversion
1266+
- glog
1267+
- hermes-engine
1268+
- RCT-Folly (= 2024.11.18.00)
1269+
- RCTRequired
1270+
- RCTTypeSafety
1271+
- React-Core
1272+
- React-debug
1273+
- React-Fabric
1274+
- React-featureflags
1275+
- React-graphics
1276+
- React-ImageManager
1277+
- react-native-safe-area-context/common (= 5.4.0)
1278+
- react-native-safe-area-context/fabric (= 5.4.0)
1279+
- React-NativeModulesApple
1280+
- React-RCTFabric
1281+
- React-rendererdebug
1282+
- React-utils
1283+
- ReactCodegen
1284+
- ReactCommon/turbomodule/bridging
1285+
- ReactCommon/turbomodule/core
1286+
- Yoga
1287+
- react-native-safe-area-context/common (5.4.0):
1288+
- DoubleConversion
1289+
- glog
1290+
- hermes-engine
1291+
- RCT-Folly (= 2024.11.18.00)
1292+
- RCTRequired
1293+
- RCTTypeSafety
1294+
- React-Core
1295+
- React-debug
1296+
- React-Fabric
1297+
- React-featureflags
1298+
- React-graphics
1299+
- React-ImageManager
1300+
- React-NativeModulesApple
1301+
- React-RCTFabric
1302+
- React-rendererdebug
1303+
- React-utils
1304+
- ReactCodegen
1305+
- ReactCommon/turbomodule/bridging
1306+
- ReactCommon/turbomodule/core
1307+
- Yoga
1308+
- react-native-safe-area-context/fabric (5.4.0):
1309+
- DoubleConversion
1310+
- glog
1311+
- hermes-engine
1312+
- RCT-Folly (= 2024.11.18.00)
1313+
- RCTRequired
1314+
- RCTTypeSafety
1315+
- React-Core
1316+
- React-debug
1317+
- React-Fabric
1318+
- React-featureflags
1319+
- React-graphics
1320+
- React-ImageManager
1321+
- react-native-safe-area-context/common
1322+
- React-NativeModulesApple
1323+
- React-RCTFabric
1324+
- React-rendererdebug
1325+
- React-utils
1326+
- ReactCodegen
1327+
- ReactCommon/turbomodule/bridging
1328+
- ReactCommon/turbomodule/core
1329+
- Yoga
12641330
- react-native-version-info (1.1.1):
12651331
- React-Core
12661332
- react-native-webview (13.13.5):
@@ -1701,6 +1767,7 @@ DEPENDENCIES:
17011767
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
17021768
- react-native-fingerprint-scanner (from `../node_modules/react-native-fingerprint-scanner`)
17031769
- react-native-mmkv (from `../node_modules/react-native-mmkv`)
1770+
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
17041771
- react-native-version-info (from `../node_modules/react-native-version-info`)
17051772
- react-native-webview (from `../node_modules/react-native-webview`)
17061773
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
@@ -1829,6 +1896,8 @@ EXTERNAL SOURCES:
18291896
:path: "../node_modules/react-native-fingerprint-scanner"
18301897
react-native-mmkv:
18311898
:path: "../node_modules/react-native-mmkv"
1899+
react-native-safe-area-context:
1900+
:path: "../node_modules/react-native-safe-area-context"
18321901
react-native-version-info:
18331902
:path: "../node_modules/react-native-version-info"
18341903
react-native-webview:
@@ -1952,6 +2021,7 @@ SPEC CHECKSUMS:
19522021
React-microtasksnativemodule: b740ebae7fad70aa78bc24cc0dfc15ac9637b6eb
19532022
react-native-fingerprint-scanner: 562585260768cad51ae48e4b505d28c8731aecfa
19542023
react-native-mmkv: 24917f0685641ccfd37c9b7004b26c00551128be
2024+
react-native-safe-area-context: afcc2e2b3e78ae8ef90d81e658aacee34ebc27ea
19552025
react-native-version-info: f0b04e16111c4016749235ff6d9a757039189141
19562026
react-native-webview: 5095dd03fc98a529e44a6bb81aed21062b5f879e
19572027
React-NativeModulesApple: af0571ac115d09c9a669ed45ce6a4ca960598a2d

packages/mobile/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
},
7777
"dependencies": {
7878
"@notifee/react-native": "^9.1.8",
79+
"react-native-safe-area-context": "^5.4.0",
7980
"react-native-store-review": "^0.4.3"
8081
}
8182
}
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import React from 'react'
22
import { MobileWebAppContainer } from './MobileWebAppContainer'
3-
4-
const AppComponent: React.FC = () => {
5-
return <MobileWebAppContainer />
6-
}
3+
import { SafeAreaProvider } from 'react-native-safe-area-context'
74

85
export const MobileWebApp = () => {
9-
return <AppComponent />
6+
return (
7+
<SafeAreaProvider>
8+
<MobileWebAppContainer />
9+
</SafeAreaProvider>
10+
)
1011
}

packages/mobile/src/MobileWebAppContainer.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { IsDev } from './Lib/Utils'
1515
import { ReceivedSharedItemsHandler } from './ReceivedSharedItemsHandler'
1616
import { ReviewService } from './ReviewService'
1717
import notifee, { EventType } from '@notifee/react-native'
18+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
1819

1920
const LoggingEnabled = IsDev
2021

@@ -96,6 +97,12 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
9697
// iOS handles this using the `willChangeFrame` event instead
9798
if (Platform.OS === 'android') {
9899
fireKeyboardSizeChangeEvent(e)
100+
webViewRef.current?.postMessage(
101+
JSON.stringify({
102+
reactNativeEvent: ReactNativeToWebEvent.KeyboardDidShow,
103+
messageType: 'event',
104+
}),
105+
)
99106
}
100107
device.reloadStatusBarStyle(false)
101108
})
@@ -344,14 +351,31 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
344351
receivedSharedItemsHandlerInstance.deinit()
345352
}
346353
}, [])
354+
355+
const [didAppLaunch, setDidAppLaunch] = useState(false)
347356
useEffect(() => {
348357
return device.addApplicationEventReceiver((event) => {
349358
if (event === ApplicationEvent.Launched) {
350359
receivedSharedItemsHandler.current.setIsApplicationLaunched(true)
360+
setDidAppLaunch(true)
351361
}
352362
})
353363
}, [device])
354364

365+
const insets = useSafeAreaInsets()
366+
useEffect(() => {
367+
if (!didAppLaunch) {
368+
return
369+
}
370+
webViewRef.current?.postMessage(
371+
JSON.stringify({
372+
reactNativeEvent: ReactNativeToWebEvent.UpdateSafeAreaInsets,
373+
messageType: 'event',
374+
messageData: insets,
375+
}),
376+
)
377+
}, [didAppLaunch, insets])
378+
355379
const [didLoadEnd, setDidLoadEnd] = useState(false)
356380

357381
if (showAndroidWebviewUpdatePrompt) {

packages/snjs/lib/Client/ReactNativeToWebEvent.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ export enum ReactNativeToWebEvent {
88
KeyboardSizeChanged = 'KeyboardSizeChanged',
99
KeyboardWillShow = 'KeyboardWillShow',
1010
KeyboardWillHide = 'KeyboardWillHide',
11+
KeyboardDidShow = 'KeyboardDidShow',
1112
KeyboardDidHide = 'KeyboardDidHide',
13+
UpdateSafeAreaInsets = 'UpdateSafeAreaInsets',
1214
ReceivedFile = 'ReceivedFile',
1315
ReceivedLink = 'ReceivedLink',
1416
ReceivedText = 'ReceivedText',

packages/ui-services/src/WebApplication/WebApplicationInterface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface WebApplicationInterface extends ApplicationInterface {
2121
isFloatingKeyboard: boolean
2222
}): void
2323
handleMobileKeyboardDidHideEvent(): void
24+
handleUpdateSafeAreaInsets(insets: { top: number; bottom: number; left: number; right: number }): void
2425
handleReceivedFileEvent(file: { name: string; mimeType: string; data: string }): void
2526
handleReceivedTextEvent(item: { text: string; title?: string }): Promise<void>
2627
handleReceivedLinkEvent(item: { link: string; title: string }): Promise<void>

packages/web/src/javascripts/Application/WebApplication.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,12 @@ export class WebApplication extends SNApplication implements WebApplicationInter
361361
setCustomViewportHeight(100, 'vh', true)
362362
}
363363

364+
handleUpdateSafeAreaInsets(insets: { top: number; bottom: number; left: number; right: number }) {
365+
for (const [key, value] of Object.entries(insets)) {
366+
document.documentElement.style.setProperty(`--safe-area-inset-${key}`, `${value}px`)
367+
}
368+
}
369+
364370
handleOpenFilePreviewEvent({ id }: { id: string }): void {
365371
const file = this.items.findItem<FileItem>(id)
366372
if (!file) {

packages/web/src/javascripts/Components/ContentListView/FloatingAddButton.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { classNames } from '@standardnotes/snjs'
22
import { ButtonStyle, getColorsForPrimaryVariant } from '../Button/Button'
33
import Icon from '../Icon/Icon'
4+
import { useAvailableSafeAreaPadding } from '@/Hooks/useSafeAreaPadding'
45

56
type Props = {
67
label: string
@@ -14,12 +15,15 @@ const PropertiesRequiredForFixedPositioningToWorkOnIOSSafari: React.CSSPropertie
1415

1516
const FloatingAddButton = ({ label, style, onClick }: Props) => {
1617
const buttonClasses = getColorsForPrimaryVariant(style)
18+
const { hasBottomInset } = useAvailableSafeAreaPadding()
19+
1720
return (
1821
<button
1922
className={classNames(
20-
'fixed bottom-6 right-6 z-editor-title-bar ml-3 flex h-15 w-15 cursor-pointer items-center',
23+
'fixed right-6 z-editor-title-bar ml-3 flex h-15 w-15 cursor-pointer items-center',
2124
`justify-center rounded-full border border-solid border-transparent ${buttonClasses}`,
2225
'hover:brightness-125',
26+
hasBottomInset ? 'bottom-[calc(var(--safe-area-inset-bottom)+0.5rem)]' : 'bottom-6',
2327
)}
2428
title={label}
2529
aria-label={label}
Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { ReactNativeToWebEvent } from '@standardnotes/snjs'
2+
import { useApplication } from '@/Components/ApplicationProvider'
3+
import { useEffect, useState } from 'react'
4+
15
export const useAvailableSafeAreaPadding = () => {
26
const documentStyle = getComputedStyle(document.documentElement)
37

@@ -6,10 +10,22 @@ export const useAvailableSafeAreaPadding = () => {
610
const bottom = parseInt(documentStyle.getPropertyValue('--safe-area-inset-bottom'))
711
const left = parseInt(documentStyle.getPropertyValue('--safe-area-inset-left'))
812

13+
const application = useApplication()
14+
const [isKeyboardVisible, setIsKeyboardVisible] = useState(false)
15+
useEffect(() => {
16+
return application.addNativeMobileEventListener((event) => {
17+
if (event === ReactNativeToWebEvent.KeyboardDidShow) {
18+
setIsKeyboardVisible(true)
19+
} else if (event === ReactNativeToWebEvent.KeyboardDidHide) {
20+
setIsKeyboardVisible(false)
21+
}
22+
})
23+
}, [application])
24+
925
return {
1026
hasTopInset: !!top,
1127
hasRightInset: !!right,
12-
hasBottomInset: !!bottom,
28+
hasBottomInset: !!bottom && !isKeyboardVisible,
1329
hasLeftInset: !!left,
1430
}
1531
}

0 commit comments

Comments
 (0)