diff --git a/.yarn/cache/react-native-safe-area-context-npm-5.4.0-835ad1d4d4-7d7f9a8278.zip b/.yarn/cache/react-native-safe-area-context-npm-5.4.0-835ad1d4d4-7d7f9a8278.zip
new file mode 100644
index 000000000..c9b3d7b6c
Binary files /dev/null and b/.yarn/cache/react-native-safe-area-context-npm-5.4.0-835ad1d4d4-7d7f9a8278.zip differ
diff --git a/packages/mobile/ios/Podfile.lock b/packages/mobile/ios/Podfile.lock
index 400055055..24d816e82 100644
--- a/packages/mobile/ios/Podfile.lock
+++ b/packages/mobile/ios/Podfile.lock
@@ -1261,6 +1261,72 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
+ - react-native-safe-area-context (5.4.0):
+ - DoubleConversion
+ - glog
+ - hermes-engine
+ - RCT-Folly (= 2024.11.18.00)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-ImageManager
+ - react-native-safe-area-context/common (= 5.4.0)
+ - react-native-safe-area-context/fabric (= 5.4.0)
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - Yoga
+ - react-native-safe-area-context/common (5.4.0):
+ - DoubleConversion
+ - glog
+ - hermes-engine
+ - RCT-Folly (= 2024.11.18.00)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-ImageManager
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - Yoga
+ - react-native-safe-area-context/fabric (5.4.0):
+ - DoubleConversion
+ - glog
+ - hermes-engine
+ - RCT-Folly (= 2024.11.18.00)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-ImageManager
+ - react-native-safe-area-context/common
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - Yoga
- react-native-version-info (1.1.1):
- React-Core
- react-native-webview (13.13.5):
@@ -1701,6 +1767,7 @@ DEPENDENCIES:
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
- react-native-fingerprint-scanner (from `../node_modules/react-native-fingerprint-scanner`)
- react-native-mmkv (from `../node_modules/react-native-mmkv`)
+ - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- react-native-version-info (from `../node_modules/react-native-version-info`)
- react-native-webview (from `../node_modules/react-native-webview`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
@@ -1829,6 +1896,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-fingerprint-scanner"
react-native-mmkv:
:path: "../node_modules/react-native-mmkv"
+ react-native-safe-area-context:
+ :path: "../node_modules/react-native-safe-area-context"
react-native-version-info:
:path: "../node_modules/react-native-version-info"
react-native-webview:
@@ -1952,6 +2021,7 @@ SPEC CHECKSUMS:
React-microtasksnativemodule: b740ebae7fad70aa78bc24cc0dfc15ac9637b6eb
react-native-fingerprint-scanner: 562585260768cad51ae48e4b505d28c8731aecfa
react-native-mmkv: 24917f0685641ccfd37c9b7004b26c00551128be
+ react-native-safe-area-context: afcc2e2b3e78ae8ef90d81e658aacee34ebc27ea
react-native-version-info: f0b04e16111c4016749235ff6d9a757039189141
react-native-webview: 5095dd03fc98a529e44a6bb81aed21062b5f879e
React-NativeModulesApple: af0571ac115d09c9a669ed45ce6a4ca960598a2d
diff --git a/packages/mobile/package.json b/packages/mobile/package.json
index f61501ce3..ae0e5881d 100644
--- a/packages/mobile/package.json
+++ b/packages/mobile/package.json
@@ -76,6 +76,7 @@
},
"dependencies": {
"@notifee/react-native": "^9.1.8",
+ "react-native-safe-area-context": "^5.4.0",
"react-native-store-review": "^0.4.3"
}
}
diff --git a/packages/mobile/src/MobileWebApp.tsx b/packages/mobile/src/MobileWebApp.tsx
index 9ae19532f..4043e42fb 100644
--- a/packages/mobile/src/MobileWebApp.tsx
+++ b/packages/mobile/src/MobileWebApp.tsx
@@ -1,10 +1,11 @@
import React from 'react'
import { MobileWebAppContainer } from './MobileWebAppContainer'
-
-const AppComponent: React.FC = () => {
- return
-}
+import { SafeAreaProvider } from 'react-native-safe-area-context'
export const MobileWebApp = () => {
- return
+ return (
+
+
+
+ )
}
diff --git a/packages/mobile/src/MobileWebAppContainer.tsx b/packages/mobile/src/MobileWebAppContainer.tsx
index 0343a26cf..1068a6bfa 100644
--- a/packages/mobile/src/MobileWebAppContainer.tsx
+++ b/packages/mobile/src/MobileWebAppContainer.tsx
@@ -15,6 +15,7 @@ import { IsDev } from './Lib/Utils'
import { ReceivedSharedItemsHandler } from './ReceivedSharedItemsHandler'
import { ReviewService } from './ReviewService'
import notifee, { EventType } from '@notifee/react-native'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
const LoggingEnabled = IsDev
@@ -96,6 +97,12 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
// iOS handles this using the `willChangeFrame` event instead
if (Platform.OS === 'android') {
fireKeyboardSizeChangeEvent(e)
+ webViewRef.current?.postMessage(
+ JSON.stringify({
+ reactNativeEvent: ReactNativeToWebEvent.KeyboardDidShow,
+ messageType: 'event',
+ }),
+ )
}
device.reloadStatusBarStyle(false)
})
@@ -344,14 +351,31 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
receivedSharedItemsHandlerInstance.deinit()
}
}, [])
+
+ const [didAppLaunch, setDidAppLaunch] = useState(false)
useEffect(() => {
return device.addApplicationEventReceiver((event) => {
if (event === ApplicationEvent.Launched) {
receivedSharedItemsHandler.current.setIsApplicationLaunched(true)
+ setDidAppLaunch(true)
}
})
}, [device])
+ const insets = useSafeAreaInsets()
+ useEffect(() => {
+ if (!didAppLaunch) {
+ return
+ }
+ webViewRef.current?.postMessage(
+ JSON.stringify({
+ reactNativeEvent: ReactNativeToWebEvent.UpdateSafeAreaInsets,
+ messageType: 'event',
+ messageData: insets,
+ }),
+ )
+ }, [didAppLaunch, insets])
+
const [didLoadEnd, setDidLoadEnd] = useState(false)
if (showAndroidWebviewUpdatePrompt) {
diff --git a/packages/snjs/lib/Client/ReactNativeToWebEvent.ts b/packages/snjs/lib/Client/ReactNativeToWebEvent.ts
index a4032f6f0..6f28ad15c 100644
--- a/packages/snjs/lib/Client/ReactNativeToWebEvent.ts
+++ b/packages/snjs/lib/Client/ReactNativeToWebEvent.ts
@@ -8,7 +8,9 @@ export enum ReactNativeToWebEvent {
KeyboardSizeChanged = 'KeyboardSizeChanged',
KeyboardWillShow = 'KeyboardWillShow',
KeyboardWillHide = 'KeyboardWillHide',
+ KeyboardDidShow = 'KeyboardDidShow',
KeyboardDidHide = 'KeyboardDidHide',
+ UpdateSafeAreaInsets = 'UpdateSafeAreaInsets',
ReceivedFile = 'ReceivedFile',
ReceivedLink = 'ReceivedLink',
ReceivedText = 'ReceivedText',
diff --git a/packages/ui-services/src/WebApplication/WebApplicationInterface.ts b/packages/ui-services/src/WebApplication/WebApplicationInterface.ts
index f2d2bac3d..0dd48f766 100644
--- a/packages/ui-services/src/WebApplication/WebApplicationInterface.ts
+++ b/packages/ui-services/src/WebApplication/WebApplicationInterface.ts
@@ -21,6 +21,7 @@ export interface WebApplicationInterface extends ApplicationInterface {
isFloatingKeyboard: boolean
}): void
handleMobileKeyboardDidHideEvent(): void
+ handleUpdateSafeAreaInsets(insets: { top: number; bottom: number; left: number; right: number }): void
handleReceivedFileEvent(file: { name: string; mimeType: string; data: string }): void
handleReceivedTextEvent(item: { text: string; title?: string }): Promise
handleReceivedLinkEvent(item: { link: string; title: string }): Promise
diff --git a/packages/web/src/javascripts/Application/WebApplication.ts b/packages/web/src/javascripts/Application/WebApplication.ts
index 46c91062b..6a52ec3f1 100644
--- a/packages/web/src/javascripts/Application/WebApplication.ts
+++ b/packages/web/src/javascripts/Application/WebApplication.ts
@@ -361,6 +361,12 @@ export class WebApplication extends SNApplication implements WebApplicationInter
setCustomViewportHeight(100, 'vh', true)
}
+ handleUpdateSafeAreaInsets(insets: { top: number; bottom: number; left: number; right: number }) {
+ for (const [key, value] of Object.entries(insets)) {
+ document.documentElement.style.setProperty(`--safe-area-inset-${key}`, `${value}px`)
+ }
+ }
+
handleOpenFilePreviewEvent({ id }: { id: string }): void {
const file = this.items.findItem(id)
if (!file) {
diff --git a/packages/web/src/javascripts/Components/ContentListView/FloatingAddButton.tsx b/packages/web/src/javascripts/Components/ContentListView/FloatingAddButton.tsx
index 851b18d4c..b4abd42f3 100644
--- a/packages/web/src/javascripts/Components/ContentListView/FloatingAddButton.tsx
+++ b/packages/web/src/javascripts/Components/ContentListView/FloatingAddButton.tsx
@@ -1,6 +1,7 @@
import { classNames } from '@standardnotes/snjs'
import { ButtonStyle, getColorsForPrimaryVariant } from '../Button/Button'
import Icon from '../Icon/Icon'
+import { useAvailableSafeAreaPadding } from '@/Hooks/useSafeAreaPadding'
type Props = {
label: string
@@ -14,12 +15,15 @@ const PropertiesRequiredForFixedPositioningToWorkOnIOSSafari: React.CSSPropertie
const FloatingAddButton = ({ label, style, onClick }: Props) => {
const buttonClasses = getColorsForPrimaryVariant(style)
+ const { hasBottomInset } = useAvailableSafeAreaPadding()
+
return (