diff --git a/packages/mobile/android/app/src/main/java/com/standardnotes/CustomWebViewManager.java b/packages/mobile/android/app/src/main/java/com/standardnotes/CustomWebViewManager.java new file mode 100644 index 000000000..c2e0a8c59 --- /dev/null +++ b/packages/mobile/android/app/src/main/java/com/standardnotes/CustomWebViewManager.java @@ -0,0 +1,74 @@ +package com.standardnotes; + +import com.reactnativecommunity.webview.RNCWebViewManager; +import com.facebook.react.uimanager.ThemedReactContext; +import android.view.inputmethod.InputConnectionWrapper; +import com.facebook.react.module.annotations.ReactModule; +import android.view.KeyEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.webkit.WebView; + +@ReactModule(name = CustomWebViewManager.REACT_CLASS) +public class CustomWebViewManager extends RNCWebViewManager { + /* This name must match what we’re referring to in JS */ + protected static final String REACT_CLASS = "CustomWebView"; + + protected static class CustomWebViewClient extends RNCWebViewClient {} + + protected static class CustomWebView extends RNCWebView { + public CustomWebView(ThemedReactContext reactContext) { + super(reactContext); + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + InputConnection con = super.onCreateInputConnection(outAttrs); + if (con == null) { + return null; + } + return new CustomInputConnection(con, true); + } + + private class CustomInputConnection extends InputConnectionWrapper { + public CustomInputConnection(InputConnection target, boolean mutable) { + super(target, mutable); + } + + @Override + public boolean sendKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) { + // Un-comment if you wish to cancel the backspace: + // return false; + } + return super.sendKeyEvent(event); + } + + @Override + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + // magic: in latest Android, deleteSurroundingText(1, 0) will be called for backspace + if (beforeLength == 1 && afterLength == 0) { + // backspace + return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) + && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); + } + return super.deleteSurroundingText(beforeLength, afterLength); + } + } + } + + @Override + protected RNCWebView createRNCWebViewInstance(ThemedReactContext reactContext) { + return new CustomWebView(reactContext); + } + + @Override + public String getName() { + return REACT_CLASS; + } + + @Override + protected void addEventEmitters(ThemedReactContext reactContext, WebView view) { + view.setWebViewClient(new CustomWebViewClient()); + } +} \ No newline at end of file diff --git a/packages/mobile/android/app/src/main/java/com/standardnotes/CustomWebViewPackage.java b/packages/mobile/android/app/src/main/java/com/standardnotes/CustomWebViewPackage.java new file mode 100644 index 000000000..dc58e3948 --- /dev/null +++ b/packages/mobile/android/app/src/main/java/com/standardnotes/CustomWebViewPackage.java @@ -0,0 +1,22 @@ +package com.standardnotes; + +import java.util.Arrays; +import java.util.Collections; +import java.util.ArrayList; +import java.util.List; +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +public class CustomWebViewPackage implements ReactPackage { + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Arrays.asList(new CustomWebViewManager()); + } +} \ No newline at end of file diff --git a/packages/mobile/android/app/src/main/java/com/standardnotes/MainApplication.java b/packages/mobile/android/app/src/main/java/com/standardnotes/MainApplication.java index fd6570cc7..7461358fa 100644 --- a/packages/mobile/android/app/src/main/java/com/standardnotes/MainApplication.java +++ b/packages/mobile/android/app/src/main/java/com/standardnotes/MainApplication.java @@ -38,6 +38,7 @@ public class MainApplication extends Application implements ReactApplication { List packages = new PackageList(this).getPackages(); packages.add(new Fido2ApiPackage()); + packages.add(new CustomWebViewPackage()); return packages; } diff --git a/packages/mobile/src/MobileWebAppContainer.tsx b/packages/mobile/src/MobileWebAppContainer.tsx index 7d1662ec6..3c4027bba 100644 --- a/packages/mobile/src/MobileWebAppContainer.tsx +++ b/packages/mobile/src/MobileWebAppContainer.tsx @@ -1,15 +1,17 @@ import { ReactNativeToWebEvent } from '@standardnotes/snjs' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { Button, Keyboard, Platform, Text, View } from 'react-native' +import { Button, Keyboard, Platform, requireNativeComponent, Text, View } from 'react-native' import VersionInfo from 'react-native-version-info' import { WebView, WebViewMessageEvent } from 'react-native-webview' -import { OnShouldStartLoadWithRequest } from 'react-native-webview/lib/WebViewTypes' +import { NativeWebViewAndroid, OnShouldStartLoadWithRequest } from 'react-native-webview/lib/WebViewTypes' import { AndroidBackHandlerService } from './AndroidBackHandlerService' import { AppStateObserverService } from './AppStateObserverService' import { ColorSchemeObserverService } from './ColorSchemeObserverService' import { MobileDevice, MobileDeviceEvent } from './Lib/MobileDevice' import { IsDev } from './Lib/Utils' +const CustomWebView: NativeWebViewAndroid = requireNativeComponent('CustomWebView') + const LoggingEnabled = IsDev export const MobileWebAppContainer = () => { @@ -354,6 +356,12 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo * This is needed to prevent the keyboard from pushing the webview up and down when it appears and disappears. */ scrollEnabled={false} + overScrollMode="never" + nativeConfig={Platform.select({ + android: { + component: CustomWebView, + }, + })} /> ) } diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/MobileToolbarPlugin/MobileToolbarPlugin.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/MobileToolbarPlugin/MobileToolbarPlugin.tsx index 4b90ca1d4..9e89226ca 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/MobileToolbarPlugin/MobileToolbarPlugin.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/MobileToolbarPlugin/MobileToolbarPlugin.tsx @@ -4,7 +4,7 @@ import useModal from '../../Lexical/Hooks/useModal' import { InsertTableDialog } from '../../Plugins/TablePlugin' import { getSelectedNode } from '../../Lexical/Utils/getSelectedNode' import { sanitizeUrl } from '../../Lexical/Utils/sanitizeUrl' -import { $getSelection, $isRangeSelection, DELETE_CHARACTER_COMMAND, FORMAT_TEXT_COMMAND } from 'lexical' +import { $getSelection, $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical' import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { GetAlignmentBlocks } from '../Blocks/Alignment' @@ -23,14 +23,11 @@ import { GetPasswordBlock } from '../Blocks/Password' import { GetQuoteBlock } from '../Blocks/Quote' import { GetTableBlock } from '../Blocks/Table' import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery' -import { classNames, Platform } from '@standardnotes/snjs' +import { classNames } from '@standardnotes/snjs' import { SUPER_TOGGLE_SEARCH } from '@standardnotes/ui-services' import { useApplication } from '@/Components/ApplicationProvider' import { GetRemoteImageBlock } from '../Blocks/RemoteImage' import { InsertRemoteImageDialog } from '../RemoteImagePlugin/RemoteImagePlugin' -import { SKAlert } from '@standardnotes/styles' - -const DontShowSuperAndroidBackspaceAlertKey = 'dontShowSuperAndroidBackspaceAlert' const MobileToolbarPlugin = () => { const application = useApplication() @@ -40,7 +37,6 @@ const MobileToolbarPlugin = () => { const [isInEditor, setIsInEditor] = useState(false) const [isInToolbar, setIsInToolbar] = useState(false) const isMobile = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm) - const isAndroid = application.platform === Platform.Android const toolbarRef = useRef(null) const backspaceButtonRef = useRef(null) @@ -204,38 +200,6 @@ const MobileToolbarPlugin = () => { } }, []) - useEffect(() => { - if (!isAndroid) { - return - } - - const dontShowAgain = application.getValue(DontShowSuperAndroidBackspaceAlertKey) - - if (dontShowAgain) { - return - } - - const alert = new SKAlert({ - title: 'Android backspace issue', - text: 'There is a known issue with Super on Android where pressing the backspace will also delete the character after the cursor. We are working on a fix for this. Please use the backspace button in the toolbar as a workaround.', - buttons: [ - { - text: 'OK', - style: 'default', - }, - { - text: "Don't show again", - style: 'default', - action: () => { - application.setValue(DontShowSuperAndroidBackspaceAlertKey, true) - }, - }, - ], - }) - - alert.present() - }, [application, isAndroid]) - const isFocusInEditorOrToolbar = isInEditor || isInToolbar if (!isMobile || !isFocusInEditorOrToolbar) { return null @@ -269,20 +233,6 @@ const MobileToolbarPlugin = () => { > - {isAndroid && ( - - )} )