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 (