From c30e6d80b11b155c8ca8762eaa3eca50a8937443 Mon Sep 17 00:00:00 2001 From: Jason Johnston Date: Wed, 10 May 2023 14:09:31 -0400 Subject: [PATCH] [RNMobile] Add embed webview for Android (#50440) * Add Gutenberg Embed WebView * Clean up * Rename method Fix renaming --ammend * Remove commented out code * Removed unused method * Remove unused menu layout * Update load and get title methods These are no longer overriden in the main Android app --------- Co-authored-by: jhnstn --- .../src/main/AndroidManifest.xml | 3 + .../GutenbergBridgeJS2Parent.java | 2 + .../GutenbergEmbedWebViewActivity.java | 163 ++++++++++++++++++ .../RNReactNativeGutenbergBridgeModule.java | 5 + .../WPAndroidGlue/WPAndroidGlueCode.java | 12 ++ .../activity_gutenberg_embed_web_view.xml | 44 +++++ packages/react-native-bridge/index.js | 14 +- .../java/com/gutenberg/MainApplication.java | 5 + 8 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergEmbedWebViewActivity.java create mode 100644 packages/react-native-bridge/android/react-native-bridge/src/main/res/layout/activity_gutenberg_embed_web_view.xml diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/AndroidManifest.xml b/packages/react-native-bridge/android/react-native-bridge/src/main/AndroidManifest.xml index c4d5efc7496118..7d31a2d03bd6cf 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/AndroidManifest.xml +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/AndroidManifest.xml @@ -20,6 +20,9 @@ + diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java index 8991486676a705..f5c0d325990b46 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java @@ -146,6 +146,8 @@ void gutenbergDidRequestUnsupportedBlockFallback(ReplaceUnsupportedBlockCallback String blockName, String blockTitle); + void requestEmbedFullscreenPreview(String content, String title); + void gutenbergDidSendButtonPressedAction(String buttonType); void onShowUserSuggestions(Consumer onResult); diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergEmbedWebViewActivity.java b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergEmbedWebViewActivity.java new file mode 100644 index 00000000000000..30125a853ad0fb --- /dev/null +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergEmbedWebViewActivity.java @@ -0,0 +1,163 @@ +package org.wordpress.mobile.ReactNativeGutenbergBridge; + +import android.annotation.SuppressLint; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Handler; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.webkit.CookieManager; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.ProgressBar; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class GutenbergEmbedWebViewActivity extends AppCompatActivity { + public static final String ARG_CONTENT = "content"; + public static final String ARG_TITLE = "title"; + private static final String JAVA_SCRIPT_INTERFACE_NAME = "wpwebkit"; + + protected WebView mWebView; + + private ProgressBar mProgressBar; + private AtomicBoolean mIsWebPageLoaded = new AtomicBoolean(false); + private final Handler mWebPageLoadedHandler = new Handler(); + private final Runnable mWebPageLoadedRunnable = new Runnable() { + @Override public void run() { + if (!mIsWebPageLoaded.getAndSet(true)) { + mProgressBar.setVisibility(View.GONE); + } + } + }; + + @SuppressLint("SetJavaScriptEnabled") + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_gutenberg_embed_web_view); + + setupToolbar(); + + mWebView = findViewById(R.id.embed_web_view); + + mProgressBar = findViewById(R.id.progress_bar); + + // Set settings + WebSettings settings = mWebView.getSettings(); + settings.setJavaScriptEnabled(true); + + // Setup WebView client + setupWebViewClient(); + + // Setup Web Chrome client + mWebView.setWebChromeClient(new WebChromeClient() { + @Override + public void onProgressChanged(WebView view, int progress) { + if (progress == 100) { + mWebPageLoadedHandler.removeCallbacks(mWebPageLoadedRunnable); + mWebPageLoadedHandler.postDelayed(mWebPageLoadedRunnable, 1500); + } else { + mIsWebPageLoaded.compareAndSet(true, false); + if (mProgressBar.getVisibility() == View.GONE) { + mProgressBar.setVisibility(View.VISIBLE); + } + mProgressBar.setProgress(progress); + } + } + }); + + load(); + } + + protected void load() { + String content = getIntent().getExtras().getString(ARG_CONTENT); + mWebView.loadData(content, "text/html", "UTF-8"); + } + + private void setupToolbar() { + setTitle(""); + + Toolbar toolbar = findViewById(R.id.toolbar); + if (toolbar != null) { + setSupportActionBar(toolbar); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeAsUpIndicator(R.drawable.ic_close_24px); + actionBar.setSubtitle(""); + actionBar.setTitle(getToolbarTitle()); + } + } + } + + protected String getToolbarTitle() { + return getIntent().getExtras().getString(ARG_TITLE); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (mWebView == null) { + return false; + } + + int itemID = item.getItemId(); + + if (itemID == android.R.id.home) { + finish(); + } + + return super.onOptionsItemSelected(item); + } + + private void setupWebViewClient() { + mWebView.setWebViewClient(new WebViewClient() { + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + // Center the embed with a black background; + String css = "body{margin:0;background:#000;display:flex;align-items:center;}"; + String js = String.format("(()=>{const c='%s';const s=document.createElement('style');s.textContent=c;document.head.append(s);})()", css); + view.evaluateJavascript(js, null); + super.onPageStarted(view, url, favicon); + } + }); + } + + @Override + public void onBackPressed() { + if (mWebView.canGoBack()) { + mWebView.goBack(); + } else { + super.onBackPressed(); + } + } + + @Override + public void finish() { + runOnUiThread(() -> { + mWebView.removeJavascriptInterface(JAVA_SCRIPT_INTERFACE_NAME); + mWebView.clearHistory(); + mWebView.clearFormData(); + mWebView.clearCache(true); + mWebView.clearSslPreferences(); + }); + + super.finish(); + } + + @Override + protected void onDestroy() { + mWebPageLoadedHandler.removeCallbacks(mWebPageLoadedRunnable); + super.onDestroy(); + } +} diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java index e779f15007ba31..14a0a0a8e82771 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java @@ -367,6 +367,11 @@ public void requestUnsupportedBlockFallback(String content, String blockId, Stri replaceBlock(savedContent, savedBlockId), content, blockId, blockName, blockTitle); } + @ReactMethod + public void requestEmbedFullscreenPreview(String content, String title) { + mGutenbergBridgeJS2Parent.requestEmbedFullscreenPreview(content,title); + } + @ReactMethod public void actionButtonPressed(String buttonType) { mGutenbergBridgeJS2Parent.gutenbergDidSendButtonPressedAction(buttonType); diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java index 10e07b40ea6ee0..e270f91dbcae15 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java @@ -100,6 +100,7 @@ public class WPAndroidGlueCode { private OnImageFullscreenPreviewListener mOnImageFullscreenPreviewListener; private OnMediaEditorListener mOnMediaEditorListener; private OnGutenbergDidRequestUnsupportedBlockFallbackListener mOnGutenbergDidRequestUnsupportedBlockFallbackListener; + private OnGutenbergDidRequestEmbedFullscreenPreviewListener mOnGutenbergDidRequestEmbedFullscreenPreviewListener; private OnGutenbergDidSendButtonPressedActionListener mOnGutenbergDidSendButtonPressedActionListener; private ReplaceUnsupportedBlockCallback mReplaceUnsupportedBlockCallback; private OnMediaFilesCollectionBasedBlockEditorListener mOnMediaFilesCollectionBasedBlockEditorListener; @@ -210,6 +211,10 @@ public interface OnGutenbergDidRequestUnsupportedBlockFallbackListener { void gutenbergDidRequestUnsupportedBlockFallback(UnsupportedBlock unsupportedBlock); } + public interface OnGutenbergDidRequestEmbedFullscreenPreviewListener { + void gutenbergDidRequestEmbedFullscreenPreview(String html, String title); + } + public interface OnGutenbergDidSendButtonPressedActionListener { void gutenbergDidSendButtonPressedAction(String buttonType); } @@ -462,6 +467,11 @@ public void gutenbergDidRequestUnsupportedBlockFallback(ReplaceUnsupportedBlockC gutenbergDidRequestUnsupportedBlockFallback(new UnsupportedBlock(blockId, blockName, blockTitle, content)); } + public void requestEmbedFullscreenPreview(String html, String title) { + mOnGutenbergDidRequestEmbedFullscreenPreviewListener. + gutenbergDidRequestEmbedFullscreenPreview(html, title); + } + @Override public void gutenbergDidSendButtonPressedAction(String buttonType) { mOnGutenbergDidSendButtonPressedActionListener.gutenbergDidSendButtonPressedAction(buttonType); @@ -647,6 +657,7 @@ public void attachToContainer(ViewGroup viewGroup, OnImageFullscreenPreviewListener onImageFullscreenPreviewListener, OnMediaEditorListener onMediaEditorListener, OnGutenbergDidRequestUnsupportedBlockFallbackListener onGutenbergDidRequestUnsupportedBlockFallbackListener, + OnGutenbergDidRequestEmbedFullscreenPreviewListener onGutenbergDidRequestEmbedFullscreenPreviewListener, OnGutenbergDidSendButtonPressedActionListener onGutenbergDidSendButtonPressedActionListener, ShowSuggestionsUtil showSuggestionsUtil, OnMediaFilesCollectionBasedBlockEditorListener onMediaFilesCollectionBasedBlockEditorListener, @@ -669,6 +680,7 @@ public void attachToContainer(ViewGroup viewGroup, mOnImageFullscreenPreviewListener = onImageFullscreenPreviewListener; mOnMediaEditorListener = onMediaEditorListener; mOnGutenbergDidRequestUnsupportedBlockFallbackListener = onGutenbergDidRequestUnsupportedBlockFallbackListener; + mOnGutenbergDidRequestEmbedFullscreenPreviewListener = onGutenbergDidRequestEmbedFullscreenPreviewListener; mOnGutenbergDidSendButtonPressedActionListener = onGutenbergDidSendButtonPressedActionListener; mShowSuggestionsUtil = showSuggestionsUtil; mOnMediaFilesCollectionBasedBlockEditorListener = onMediaFilesCollectionBasedBlockEditorListener; diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/res/layout/activity_gutenberg_embed_web_view.xml b/packages/react-native-bridge/android/react-native-bridge/src/main/res/layout/activity_gutenberg_embed_web_view.xml new file mode 100644 index 00000000000000..fbbecba7d29ea8 --- /dev/null +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/res/layout/activity_gutenberg_embed_web_view.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + diff --git a/packages/react-native-bridge/index.js b/packages/react-native-bridge/index.js index 0129327903db13..d432f0de9e2383 100644 --- a/packages/react-native-bridge/index.js +++ b/packages/react-native-bridge/index.js @@ -197,7 +197,7 @@ export function requestMediaPicker( source, filter, multiple, callback ) { } /** - * Request to render an unsuported block. + * Request to render an unsupported block. * * A way to show unsupported blocks to the user is to render it on a web view. * @@ -290,6 +290,18 @@ export function requestImageFullscreenPreview( ); } +export function requestEmbedFullscreenPreview( content, title ) { + if ( isIOS ) { + /* eslint-disable-next-line no-console */ + console.warn( 'requestEmbedFullscreenPreview is not supported on iOS' ); + return; + } + return RNReactNativeGutenbergBridge.requestEmbedFullscreenPreview( + content, + title + ); +} + export function requestMediaEditor( mediaUrl, callback ) { return RNReactNativeGutenbergBridge.requestMediaEditor( mediaUrl, diff --git a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java index b8565d216f6b37..b781357bee369a 100644 --- a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java +++ b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java @@ -167,6 +167,11 @@ public void requestImageFullscreenPreview(String mediaUrl) { } + @Override + public void requestEmbedFullscreenPreview(String content, String title) { + + } + @Override public void requestMediaEditor(MediaSelectedCallback mediaSelectedCallback, String mediaUrl) {