From 24db09659dba3ba19370d9ed0ccc6fc7f61d17a9 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Mon, 27 Nov 2023 16:02:35 -0500 Subject: [PATCH 01/15] feat: Frame useNetInfo hook foundation This code is non-functioning currently. --- .../src/test/use-net-info.test.js | 27 +++++++++++++++++++ .../react-native-bridge/src/use-net-info.js | 5 ++++ 2 files changed, 32 insertions(+) create mode 100644 packages/react-native-bridge/src/test/use-net-info.test.js create mode 100644 packages/react-native-bridge/src/use-net-info.js diff --git a/packages/react-native-bridge/src/test/use-net-info.test.js b/packages/react-native-bridge/src/test/use-net-info.test.js new file mode 100644 index 0000000000000..681cf7b223afc --- /dev/null +++ b/packages/react-native-bridge/src/test/use-net-info.test.js @@ -0,0 +1,27 @@ +/** + * External dependencies + */ +import { renderHook } from 'test/helpers'; + +/** + * Internal dependencies + */ +import useNetInfo from '../use-net-info'; + +describe( 'useNetInfo', () => { + describe( 'when the network is online', () => { + it( 'should return a truthy value', () => { + const { result } = renderHook( () => useNetInfo() ); + + expect( result.current.isConnected ).toBeTruthy(); + } ); + } ); + + describe( 'when the network is offline', () => { + it( 'should return a falsy value', () => { + const { result } = renderHook( () => useNetInfo() ); + + expect( result.current.isConnected ).toBeFalsy(); + } ); + } ); +} ); diff --git a/packages/react-native-bridge/src/use-net-info.js b/packages/react-native-bridge/src/use-net-info.js new file mode 100644 index 0000000000000..1b3e95826c158 --- /dev/null +++ b/packages/react-native-bridge/src/use-net-info.js @@ -0,0 +1,5 @@ +export default function useNetInfo() { + return { + isConnected: true, + }; +} From 19a535debcb927f761db361f97992ac7e63df130 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Tue, 28 Nov 2023 13:19:47 -0500 Subject: [PATCH 02/15] feat: Add iOS connection status bridge utilities This bridge will be required for the planned JavaScript Hook to monitor connection status. --- packages/react-native-bridge/ios/Gutenberg.swift | 4 ++++ .../react-native-bridge/ios/GutenbergBridgeDelegate.swift | 2 ++ .../react-native-bridge/ios/RNReactNativeGutenbergBridge.m | 1 + .../ios/RNReactNativeGutenbergBridge.swift | 6 ++++++ .../ios/GutenbergDemo/GutenbergViewController.swift | 4 ++++ 5 files changed, 17 insertions(+) diff --git a/packages/react-native-bridge/ios/Gutenberg.swift b/packages/react-native-bridge/ios/Gutenberg.swift index 4175c1e2343c3..592c294d7c539 100644 --- a/packages/react-native-bridge/ios/Gutenberg.swift +++ b/packages/react-native-bridge/ios/Gutenberg.swift @@ -210,6 +210,10 @@ public class Gutenberg: UIResponder { bridgeModule.sendEventIfNeeded(.onRedoPressed, body: nil) } + public func connectionStatusChange(isConnected: Bool) { + bridgeModule.sendEventIfNeeded(.connectionStatusChange, body: isConnected) + } + private func properties(from editorSettings: GutenbergEditorSettings?) -> [String : Any] { var settingsUpdates = [String : Any]() settingsUpdates["isFSETheme"] = editorSettings?.isFSETheme ?? false diff --git a/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift b/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift index 83d087bccab9d..8890cd4de0f7e 100644 --- a/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift +++ b/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift @@ -283,6 +283,8 @@ public protocol GutenbergBridgeDelegate: AnyObject { func gutenbergDidRequestToggleUndoButton(_ isDisabled: Bool) func gutenbergDidRequestToggleRedoButton(_ isDisabled: Bool) + + func gutenbergDidRequestConnectionStatus() -> Bool } // MARK: - Optional GutenbergBridgeDelegate methods diff --git a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m index d333f8c1722ad..dff07a4ffc531 100644 --- a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m +++ b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m @@ -42,5 +42,6 @@ @interface RCT_EXTERN_MODULE(RNReactNativeGutenbergBridge, NSObject) RCT_EXTERN_METHOD(generateHapticFeedback) RCT_EXTERN_METHOD(toggleUndoButton:(BOOL)isDisabled) RCT_EXTERN_METHOD(toggleRedoButton:(BOOL)isDisabled) +RCT_EXTERN_METHOD(getConnectionStatus:(RCTResponseSenderBlock)callback) @end diff --git a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift index 8cf4f685bd22c..ffdef3bc560c1 100644 --- a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift +++ b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift @@ -421,6 +421,11 @@ public class RNReactNativeGutenbergBridge: RCTEventEmitter { func toggleRedoButton(_ isDisabled: Bool) { self.delegate?.gutenbergDidRequestToggleRedoButton(isDisabled) } + + @objc + func getConnectionStatus(_ callback: @escaping RCTResponseSenderBlock) { + callback([self.delegate?.gutenbergDidRequestConnectionStatus() ?? true]) + } } // MARK: - RCTBridgeModule delegate @@ -450,6 +455,7 @@ extension RNReactNativeGutenbergBridge { case showEditorHelp case onUndoPressed case onRedoPressed + case connectionStatusChange } public override func supportedEvents() -> [String]! { diff --git a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift index b269d1feb8ddf..44b84ddd0e7a8 100644 --- a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift +++ b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift @@ -345,6 +345,10 @@ extension GutenbergViewController: GutenbergBridgeDelegate { } } } + + func gutenbergDidRequestConnectionStatus() -> Bool { + return true; + } } extension GutenbergViewController: GutenbergWebDelegate { From 6dab0ecdf2439b58d20d9463a566311ae091daea Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Tue, 28 Nov 2023 13:23:59 -0500 Subject: [PATCH 03/15] feat: Add `useIsConnected` hook Provides React Hook for monitoring the network connection status via the bridge to the host app. --- packages/react-native-bridge/index.js | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/packages/react-native-bridge/index.js b/packages/react-native-bridge/index.js index 89f9f029901f9..0f7974a27721a 100644 --- a/packages/react-native-bridge/index.js +++ b/packages/react-native-bridge/index.js @@ -3,6 +3,11 @@ */ import { NativeModules, NativeEventEmitter, Platform } from 'react-native'; +/** + * WordPress dependencies + */ +import { useEffect, useState } from '@wordpress/element'; + const { RNReactNativeGutenbergBridge } = NativeModules; const isIOS = Platform.OS === 'ios'; const isAndroid = Platform.OS === 'android'; @@ -185,6 +190,49 @@ export function subscribeOnRedoPressed( callback ) { return gutenbergBridgeEvents.addListener( 'onRedoPressed', callback ); } +export function useIsConnected() { + const [ isConnected, setIsConnected ] = useState( null ); + + useEffect( () => { + let isCurrent = true; + + RNReactNativeGutenbergBridge.getConnectionStatus( + ( isBridgeConnected ) => { + if ( ! isCurrent ) { + return; + } + + setIsConnected( isBridgeConnected ); + } + ); + + return () => { + isCurrent = false; + }; + }, [] ); + + useEffect( () => { + const subscription = subscribeConnectionStatus( + ( isBridgeConnected ) => { + setIsConnected( isBridgeConnected ); + } + ); + + return () => { + subscription.remove(); + }; + }, [] ); + + return { isConnected }; +} + +function subscribeConnectionStatus( callback ) { + return gutenbergBridgeEvents.addListener( + 'connectionStatusChange', + callback + ); +} + /** * Request media picker for the given media source. * From 6270fdacabeb4b5021b85ed369a1d2470f2108dd Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Tue, 28 Nov 2023 13:31:01 -0500 Subject: [PATCH 04/15] Revert "feat: Frame useNetInfo hook foundation" This reverts commit a8d3660845457787f6b368fe0276bfcfdbd213a6. --- .../src/test/use-net-info.test.js | 27 ------------------- .../react-native-bridge/src/use-net-info.js | 5 ---- 2 files changed, 32 deletions(-) delete mode 100644 packages/react-native-bridge/src/test/use-net-info.test.js delete mode 100644 packages/react-native-bridge/src/use-net-info.js diff --git a/packages/react-native-bridge/src/test/use-net-info.test.js b/packages/react-native-bridge/src/test/use-net-info.test.js deleted file mode 100644 index 681cf7b223afc..0000000000000 --- a/packages/react-native-bridge/src/test/use-net-info.test.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * External dependencies - */ -import { renderHook } from 'test/helpers'; - -/** - * Internal dependencies - */ -import useNetInfo from '../use-net-info'; - -describe( 'useNetInfo', () => { - describe( 'when the network is online', () => { - it( 'should return a truthy value', () => { - const { result } = renderHook( () => useNetInfo() ); - - expect( result.current.isConnected ).toBeTruthy(); - } ); - } ); - - describe( 'when the network is offline', () => { - it( 'should return a falsy value', () => { - const { result } = renderHook( () => useNetInfo() ); - - expect( result.current.isConnected ).toBeFalsy(); - } ); - } ); -} ); diff --git a/packages/react-native-bridge/src/use-net-info.js b/packages/react-native-bridge/src/use-net-info.js deleted file mode 100644 index 1b3e95826c158..0000000000000 --- a/packages/react-native-bridge/src/use-net-info.js +++ /dev/null @@ -1,5 +0,0 @@ -export default function useNetInfo() { - return { - isConnected: true, - }; -} From e06e02baa705bb8a004debcf59f58ebcdc8ec2f8 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Tue, 28 Nov 2023 15:56:25 -0500 Subject: [PATCH 05/15] refactor: Align with project Swift syntax Semicolon is unnecessary. Co-authored-by: Tanner Stokes --- .../ios/GutenbergDemo/GutenbergViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift index 44b84ddd0e7a8..ef95c7e65862f 100644 --- a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift +++ b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift @@ -347,7 +347,7 @@ extension GutenbergViewController: GutenbergBridgeDelegate { } func gutenbergDidRequestConnectionStatus() -> Bool { - return true; + return true } } From 901a7615c33e53c20fa6c28e705bd6831888a6b4 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 29 Nov 2023 10:18:51 -0500 Subject: [PATCH 06/15] feat: Add Android connection status bridge utilities This bridge enables monitoring the connection status on Android. --- .../RNReactNativeGutenbergBridgeModule.java | 2 ++ .../mobile/WPAndroidGlue/DeferredEventEmitter.java | 9 +++++++++ .../mobile/WPAndroidGlue/WPAndroidGlueCode.java | 4 ++++ packages/react-native-bridge/index.js | 2 +- packages/react-native-bridge/ios/Gutenberg.swift | 3 ++- 5 files changed, 18 insertions(+), 2 deletions(-) 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 d922d863cb301..a8134be9b9b36 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 @@ -85,6 +85,8 @@ public class RNReactNativeGutenbergBridgeModule extends ReactContextBaseJavaModu public static final String MAP_KEY_FEATURED_IMAGE_ID = "featuredImageId"; + public static final String MAP_KEY_IS_CONNECTED = "isConnected"; + private boolean mIsDarkMode; public RNReactNativeGutenbergBridgeModule(ReactApplicationContext reactContext, diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/DeferredEventEmitter.java b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/DeferredEventEmitter.java index 7dd4dbf3811fe..fe83bc8a14b54 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/DeferredEventEmitter.java +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/DeferredEventEmitter.java @@ -15,6 +15,7 @@ import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_IS_CONNECTED; import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_ID; import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_NEW_ID; import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_URL; @@ -44,6 +45,8 @@ public interface JSEventEmitter { private static final String EVENT_FEATURED_IMAGE_ID_NATIVE_UPDATED = "featuredImageIdNativeUpdated"; + private static final String EVENT_CONNECTION_STATUS_CHANGE = "connectionStatusChange"; + private static final String MAP_KEY_MEDIA_FILE_STATE = "state"; private static final String MAP_KEY_MEDIA_FILE_MEDIA_ACTION_PROGRESS = "progress"; private static final String MAP_KEY_MEDIA_FILE_MEDIA_SERVER_ID = "mediaServerId"; @@ -222,6 +225,12 @@ public void sendToJSFeaturedImageId(int mediaId) { queueActionToJS(EVENT_FEATURED_IMAGE_ID_NATIVE_UPDATED, writableMap); } + public void onConnectionStatusChange(boolean isConnected) { + WritableMap writableMap = new WritableNativeMap(); + writableMap.putBoolean(MAP_KEY_IS_CONNECTED, isConnected); + queueActionToJS(EVENT_CONNECTION_STATUS_CHANGE, writableMap); + } + @Override public void onReplaceMediaFilesEditedBlock(String mediaFiles, String blockId) { WritableMap writableMap = new WritableNativeMap(); writableMap.putString(MAP_KEY_REPLACE_BLOCK_HTML, mediaFiles); 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 69adb653211da..945927db4cabe 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 @@ -1149,6 +1149,10 @@ public void sendToJSFeaturedImageId(int mediaId) { mDeferredEventEmitter.sendToJSFeaturedImageId(mediaId); } + public void connectionStatusChange(boolean isConnected) { + mDeferredEventEmitter.onConnectionStatusChange(isConnected); + } + public void replaceUnsupportedBlock(String content, String blockId) { if (mReplaceUnsupportedBlockCallback != null) { mReplaceUnsupportedBlockCallback.replaceUnsupportedBlock(content, blockId); diff --git a/packages/react-native-bridge/index.js b/packages/react-native-bridge/index.js index 0f7974a27721a..ca676942fddca 100644 --- a/packages/react-native-bridge/index.js +++ b/packages/react-native-bridge/index.js @@ -213,7 +213,7 @@ export function useIsConnected() { useEffect( () => { const subscription = subscribeConnectionStatus( - ( isBridgeConnected ) => { + ( { isConnected: isBridgeConnected } ) => { setIsConnected( isBridgeConnected ); } ); diff --git a/packages/react-native-bridge/ios/Gutenberg.swift b/packages/react-native-bridge/ios/Gutenberg.swift index 592c294d7c539..de0d1b513f00d 100644 --- a/packages/react-native-bridge/ios/Gutenberg.swift +++ b/packages/react-native-bridge/ios/Gutenberg.swift @@ -211,7 +211,8 @@ public class Gutenberg: UIResponder { } public func connectionStatusChange(isConnected: Bool) { - bridgeModule.sendEventIfNeeded(.connectionStatusChange, body: isConnected) + var data: [String: Any] = ["isConnected": isConnected] + bridgeModule.sendEventIfNeeded(.connectionStatusChange, body: data) } private func properties(from editorSettings: GutenbergEditorSettings?) -> [String : Any] { From 4fed85e4bbe0a7597a905c9e77a1da11c12fa398 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 29 Nov 2023 12:04:10 -0500 Subject: [PATCH 07/15] feat: Android network connection status request utility Allow the Android platform to request the current network connection status. --- .../GutenbergBridgeJS2Parent.java | 6 ++++++ .../RNReactNativeGutenbergBridgeModule.java | 15 +++++++++++++++ .../mobile/WPAndroidGlue/WPAndroidGlueCode.java | 13 +++++++++++++ packages/react-native-bridge/index.js | 2 +- .../ios/RNReactNativeGutenbergBridge.m | 2 +- .../ios/RNReactNativeGutenbergBridge.swift | 2 +- 6 files changed, 37 insertions(+), 3 deletions(-) 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 c6e20b29db072..c1dc4bab896b3 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 @@ -60,6 +60,10 @@ interface BlockTypeImpressionsCallback { void onRequestBlockTypeImpressions(ReadableMap impressions); } + interface ConnectionStatusCallback { + void onRequestConnectionStatus(boolean isConnected); + } + // Ref: https://github.com/facebook/react-native/blob/HEAD/Libraries/polyfills/console.js#L376 enum LogLevel { TRACE(0), @@ -183,4 +187,6 @@ void gutenbergDidRequestUnsupportedBlockFallback(ReplaceUnsupportedBlockCallback void toggleUndoButton(boolean isDisabled); void toggleRedoButton(boolean isDisabled); + + void requestConnectionStatus(ConnectionStatusCallback connectionStatusCallback); } 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 a8134be9b9b36..0073db769d9cd 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 @@ -23,6 +23,7 @@ import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.ConnectionStatusCallback; import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.MediaType; import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.OtherMediaOptionsReceivedCallback; import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.FocalPointPickerTooltipShownCallback; @@ -535,4 +536,18 @@ public void generateHapticFeedback() { } } } + + @ReactMethod + public void requestConnectionStatus(final Callback jsCallback) { + ConnectionStatusCallback connectionStatusCallback = requestConnectionStatusCallback(jsCallback); + mGutenbergBridgeJS2Parent.requestConnectionStatus(connectionStatusCallback); + } + + private ConnectionStatusCallback requestConnectionStatusCallback(final Callback jsCallback) { + return new GutenbergBridgeJS2Parent.ConnectionStatusCallback() { + @Override public void onRequestConnectionStatus(boolean isConnected) { + jsCallback.invoke(isConnected); + } + }; + } } 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 945927db4cabe..c0916d1417a34 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 @@ -112,6 +112,7 @@ public class WPAndroidGlueCode { private OnToggleUndoButtonListener mOnToggleUndoButtonListener; private OnToggleRedoButtonListener mOnToggleRedoButtonListener; + private OnConnectionStatusEventListener mOnConnectionStatusEventListener; private boolean mIsEditorMounted; private String mContentHtml = ""; @@ -259,6 +260,10 @@ public interface OnToggleRedoButtonListener { void onToggleRedoButton(boolean isDisabled); } + public interface OnConnectionStatusEventListener { + boolean onRequestConnectionStatus(); + } + public void mediaSelectionCancelled() { mAppendsMultipleSelectedToSiblingBlocks = false; } @@ -594,6 +599,12 @@ public void toggleUndoButton(boolean isDisabled) { public void toggleRedoButton(boolean isDisabled) { mOnToggleRedoButtonListener.onToggleRedoButton(isDisabled); } + + @Override + public void requestConnectionStatus(ConnectionStatusCallback connectionStatusCallback) { + boolean isConnected = mOnConnectionStatusEventListener.onRequestConnectionStatus(); + connectionStatusCallback.onRequestConnectionStatus(isConnected); + } }, mIsDarkMode); return Arrays.asList( @@ -688,6 +699,7 @@ public void attachToContainer(ViewGroup viewGroup, OnSendEventToHostListener onSendEventToHostListener, OnToggleUndoButtonListener onToggleUndoButtonListener, OnToggleRedoButtonListener onToggleRedoButtonListener, + OnConnectionStatusEventListener onConnectionStatusEventListener, boolean isDarkMode) { MutableContextWrapper contextWrapper = (MutableContextWrapper) mReactRootView.getContext(); contextWrapper.setBaseContext(viewGroup.getContext()); @@ -713,6 +725,7 @@ public void attachToContainer(ViewGroup viewGroup, mOnSendEventToHostListener = onSendEventToHostListener; mOnToggleUndoButtonListener = onToggleUndoButtonListener; mOnToggleRedoButtonListener = onToggleRedoButtonListener; + mOnConnectionStatusEventListener = onConnectionStatusEventListener; sAddCookiesInterceptor.setOnAuthHeaderRequestedListener(onAuthHeaderRequestedListener); diff --git a/packages/react-native-bridge/index.js b/packages/react-native-bridge/index.js index ca676942fddca..8e9065cc568e5 100644 --- a/packages/react-native-bridge/index.js +++ b/packages/react-native-bridge/index.js @@ -196,7 +196,7 @@ export function useIsConnected() { useEffect( () => { let isCurrent = true; - RNReactNativeGutenbergBridge.getConnectionStatus( + RNReactNativeGutenbergBridge.requestConnectionStatus( ( isBridgeConnected ) => { if ( ! isCurrent ) { return; diff --git a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m index dff07a4ffc531..3d68e51ebcacb 100644 --- a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m +++ b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.m @@ -42,6 +42,6 @@ @interface RCT_EXTERN_MODULE(RNReactNativeGutenbergBridge, NSObject) RCT_EXTERN_METHOD(generateHapticFeedback) RCT_EXTERN_METHOD(toggleUndoButton:(BOOL)isDisabled) RCT_EXTERN_METHOD(toggleRedoButton:(BOOL)isDisabled) -RCT_EXTERN_METHOD(getConnectionStatus:(RCTResponseSenderBlock)callback) +RCT_EXTERN_METHOD(requestConnectionStatus:(RCTResponseSenderBlock)callback) @end diff --git a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift index ffdef3bc560c1..ec763b2b8aaa2 100644 --- a/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift +++ b/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift @@ -423,7 +423,7 @@ public class RNReactNativeGutenbergBridge: RCTEventEmitter { } @objc - func getConnectionStatus(_ callback: @escaping RCTResponseSenderBlock) { + func requestConnectionStatus(_ callback: @escaping RCTResponseSenderBlock) { callback([self.delegate?.gutenbergDidRequestConnectionStatus() ?? true]) } } From aadf8c66ff345b176041ad82e3793191ade5d271 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 29 Nov 2023 12:24:27 -0500 Subject: [PATCH 08/15] fix: Add missing `requestConnectionStatus` bridge method mock The Demo editor fails to build without a mocked bridge method. --- .../app/src/main/java/com/gutenberg/MainApplication.java | 5 +++++ 1 file changed, 5 insertions(+) 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 d718b34f25db3..4477f1cc1d9f3 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 @@ -308,6 +308,11 @@ public void toggleRedoButton(boolean isDisabled) { mainActivity.updateRedoItem(isDisabled); } } + + @Override + public void requestConnectionStatus(ConnectionStatusCallback connectionStatusCallback) { + connectionStatusCallback.onRequestConnectionStatus(true); + } }, isDarkMode()); return new DefaultReactNativeHost(this) { From 46c122fc7bc0600ce8878c71f25b1a5b77462b25 Mon Sep 17 00:00:00 2001 From: Derek Blank Date: Thu, 7 Dec 2023 17:54:31 +1000 Subject: [PATCH 09/15] Add mobile OfflineStatus component --- .../components/offline-status/index.native.js | 46 +++++++++++++++++++ .../offline-status/style.native.scss | 27 +++++++++++ packages/icons/src/index.js | 1 + packages/icons/src/library/offline.js | 20 ++++++++ 4 files changed, 94 insertions(+) create mode 100644 packages/block-editor/src/components/offline-status/index.native.js create mode 100644 packages/block-editor/src/components/offline-status/style.native.scss create mode 100644 packages/icons/src/library/offline.js diff --git a/packages/block-editor/src/components/offline-status/index.native.js b/packages/block-editor/src/components/offline-status/index.native.js new file mode 100644 index 0000000000000..a7d51192cac0d --- /dev/null +++ b/packages/block-editor/src/components/offline-status/index.native.js @@ -0,0 +1,46 @@ +/** + * External dependencies + */ +import { Text, View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { usePreferredColorSchemeStyle } from '@wordpress/compose'; +import { Icon } from '@wordpress/components'; +import { offline as offlineIcon } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; +import { useIsConnected } from '@wordpress/react-native-bridge'; + +/** + * Internal dependencies + */ +import styles from './style'; + +const OfflineStatus = () => { + const { isConnected } = useIsConnected(); + + const containerStyle = usePreferredColorSchemeStyle( + styles.container, + styles[ 'container--dark' ] + ); + + const textStyle = usePreferredColorSchemeStyle( + styles.text, + styles[ 'text--dark' ] + ); + + const iconStyle = usePreferredColorSchemeStyle( + styles.icon, + styles[ 'icon--dark' ] + ); + + return ! isConnected ? ( + + + { __( 'Working Offline' ) } + + ) : null; +}; + +export default OfflineStatus; diff --git a/packages/block-editor/src/components/offline-status/style.native.scss b/packages/block-editor/src/components/offline-status/style.native.scss new file mode 100644 index 0000000000000..2b6e808fdbb49 --- /dev/null +++ b/packages/block-editor/src/components/offline-status/style.native.scss @@ -0,0 +1,27 @@ +.container { + background-color: $light-ultra-dim; + padding: $grid-unit; + justify-content: center; + flex-direction: row; +} + +.container--dark { + background-color: $dark-dim; +} + +.text { + padding-left: 3; + padding-top: 2; +} + +.text--dark { + color: $white; +} + +.icon { + fill: $light-secondary; +} + +.icon--dark { + fill: $dark-tertiary; +} diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js index 36b2971423442..d743299d35a24 100644 --- a/packages/icons/src/index.js +++ b/packages/icons/src/index.js @@ -190,6 +190,7 @@ export { default as postList } from './library/post-list'; export { default as postTerms } from './library/post-terms'; export { default as previous } from './library/previous'; export { default as next } from './library/next'; +export { default as offline } from './library/offline'; export { default as preformatted } from './library/preformatted'; export { default as pullLeft } from './library/pull-left'; export { default as pullRight } from './library/pull-right'; diff --git a/packages/icons/src/library/offline.js b/packages/icons/src/library/offline.js new file mode 100644 index 0000000000000..2e397fb23745a --- /dev/null +++ b/packages/icons/src/library/offline.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const offline = ( + + { /* + "no internet" by Heztasia is licensed under CCBY3.0 + https://creativecommons.org/licenses/by/3.0/ + */ } + + + + + + +); + +export default offline; From 175a742c4fc218c1dc28ad5afe41534196fc3206 Mon Sep 17 00:00:00 2001 From: Derek Blank Date: Fri, 8 Dec 2023 11:11:07 +1000 Subject: [PATCH 10/15] Add OfflineStatus component to block-list behind __DEV__ flag --- .../block-editor/src/components/block-list/index.native.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 810e23e4c1442..4c4d7914b1806 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -30,6 +30,7 @@ import { import { BlockDraggableWrapper } from '../block-draggable'; import { useEditorWrapperStyles } from '../../hooks/use-editor-wrapper-styles'; import { store as blockEditorStore } from '../../store'; +import OfflineStatus from '../offline-status'; const identity = ( x ) => x; @@ -235,6 +236,10 @@ export default function BlockList( { onLayout={ onLayout } testID="block-list-wrapper" > + { + // eslint-disable-next-line no-undef + __DEV__ && + } { isRootList ? ( Date: Mon, 11 Dec 2023 09:21:30 +0800 Subject: [PATCH 11/15] Replace offline icon and update OfflineStatus text alignment --- .../offline-status/style.native.scss | 2 +- packages/icons/src/library/offline.js | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/offline-status/style.native.scss b/packages/block-editor/src/components/offline-status/style.native.scss index 2b6e808fdbb49..714d899a7b9ca 100644 --- a/packages/block-editor/src/components/offline-status/style.native.scss +++ b/packages/block-editor/src/components/offline-status/style.native.scss @@ -2,6 +2,7 @@ background-color: $light-ultra-dim; padding: $grid-unit; justify-content: center; + align-items: center; flex-direction: row; } @@ -11,7 +12,6 @@ .text { padding-left: 3; - padding-top: 2; } .text--dark { diff --git a/packages/icons/src/library/offline.js b/packages/icons/src/library/offline.js index 2e397fb23745a..f0daa1aaeb79e 100644 --- a/packages/icons/src/library/offline.js +++ b/packages/icons/src/library/offline.js @@ -4,16 +4,18 @@ import { SVG, Path } from '@wordpress/primitives'; const offline = ( - - { /* - "no internet" by Heztasia is licensed under CCBY3.0 - https://creativecommons.org/licenses/by/3.0/ - */ } - - - - - + + ); From f039807810da1753384882bf227f833616cce25e Mon Sep 17 00:00:00 2001 From: Derek Blank Date: Mon, 11 Dec 2023 11:23:10 +0800 Subject: [PATCH 12/15] Update BEM syntax for OfflineStatus --- .../src/components/offline-status/index.native.js | 12 ++++++------ .../components/offline-status/style.native.scss | 15 ++++++++------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/src/components/offline-status/index.native.js b/packages/block-editor/src/components/offline-status/index.native.js index a7d51192cac0d..2414e4fa6d2e9 100644 --- a/packages/block-editor/src/components/offline-status/index.native.js +++ b/packages/block-editor/src/components/offline-status/index.native.js @@ -21,18 +21,18 @@ const OfflineStatus = () => { const { isConnected } = useIsConnected(); const containerStyle = usePreferredColorSchemeStyle( - styles.container, - styles[ 'container--dark' ] + styles.offline, + styles.offline__dark ); const textStyle = usePreferredColorSchemeStyle( - styles.text, - styles[ 'text--dark' ] + styles[ 'offline--text' ], + styles[ 'offline--text__dark' ] ); const iconStyle = usePreferredColorSchemeStyle( - styles.icon, - styles[ 'icon--dark' ] + styles[ 'offline--icon' ], + styles[ 'offline--icon__dark' ] ); return ! isConnected ? ( diff --git a/packages/block-editor/src/components/offline-status/style.native.scss b/packages/block-editor/src/components/offline-status/style.native.scss index 714d899a7b9ca..f72da18c6593d 100644 --- a/packages/block-editor/src/components/offline-status/style.native.scss +++ b/packages/block-editor/src/components/offline-status/style.native.scss @@ -1,4 +1,4 @@ -.container { +.offline { background-color: $light-ultra-dim; padding: $grid-unit; justify-content: center; @@ -6,22 +6,23 @@ flex-direction: row; } -.container--dark { +.offline__dark { background-color: $dark-dim; } -.text { +.offline--text { + color: $light-primary; padding-left: 3; } -.text--dark { - color: $white; +.offline--text__dark { + color: $dark-primary; } -.icon { +.offline--icon { fill: $light-secondary; } -.icon--dark { +.offline--icon__dark { fill: $dark-tertiary; } From 1bea0a320670f013b8cb9438b668e010420013b6 Mon Sep 17 00:00:00 2001 From: Derek Blank Date: Tue, 12 Dec 2023 11:11:44 +0800 Subject: [PATCH 13/15] Update OfflineStatus component colors --- .../src/components/offline-status/style.native.scss | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/offline-status/style.native.scss b/packages/block-editor/src/components/offline-status/style.native.scss index f72da18c6593d..529693516653f 100644 --- a/packages/block-editor/src/components/offline-status/style.native.scss +++ b/packages/block-editor/src/components/offline-status/style.native.scss @@ -1,5 +1,5 @@ .offline { - background-color: $light-ultra-dim; + background-color: $gray-lighten-30; padding: $grid-unit; justify-content: center; align-items: center; @@ -7,22 +7,22 @@ } .offline__dark { - background-color: $dark-dim; + background-color: $gray-70; } .offline--text { - color: $light-primary; + color: $black; padding-left: 3; } .offline--text__dark { - color: $dark-primary; + color: $white; } .offline--icon { - fill: $light-secondary; + fill: $gray-70; } .offline--icon__dark { - fill: $dark-tertiary; + fill: $gray-10; } From 281c5afc10d32143d1a515a2c65b3018134dd87f Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 13 Dec 2023 15:44:49 -0500 Subject: [PATCH 14/15] test: Import sole native stylesheet to fix test module resolution error The current Jest module resolution configuration will fail when there is only a native-specific file without a web-specific file, e.g. only a `style.native.scss` and no sibling `style.scss` next to it. Explicitly importing the native-specific file avoids the error as Jest does not attempt to seek the non-suffixed file. --- .../block-editor/src/components/offline-status/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/offline-status/index.native.js b/packages/block-editor/src/components/offline-status/index.native.js index 2414e4fa6d2e9..ae6007e75103c 100644 --- a/packages/block-editor/src/components/offline-status/index.native.js +++ b/packages/block-editor/src/components/offline-status/index.native.js @@ -15,7 +15,7 @@ import { useIsConnected } from '@wordpress/react-native-bridge'; /** * Internal dependencies */ -import styles from './style'; +import styles from './style.native.scss'; const OfflineStatus = () => { const { isConnected } = useIsConnected(); From 5b32144f598378d6d4b7819a99f33135f8d8530d Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 13 Dec 2023 16:20:28 -0500 Subject: [PATCH 15/15] test: Mock `useIsConnected` native bridge method The native implementation is not available within the JavaScript testing environment. --- test/native/setup.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/native/setup.js b/test/native/setup.js index 3770a4ce3efc6..0f4c9f9eda20c 100644 --- a/test/native/setup.js +++ b/test/native/setup.js @@ -107,6 +107,7 @@ jest.mock( '@wordpress/react-native-bridge', () => { subscribeShowEditorHelp: jest.fn(), subscribeOnUndoPressed: jest.fn(), subscribeOnRedoPressed: jest.fn(), + useIsConnected: jest.fn( () => ( { isConnected: true } ) ), editorDidMount: jest.fn(), editorDidAutosave: jest.fn(), subscribeMediaUpload: jest.fn(),