Skip to content

Commit

Permalink
[Mobile] - Move Undo/Redo buttons (#51766)
Browse files Browse the repository at this point in the history
* Mobile - Move Undo/Redo buttons

* Mobile - Add undo/redo buttons and Toolbar to the Android demo app

* Update Demo Android app icons

* Update Demo iOS app icons and header

* Android demo app - Update initial props

* Update test util to use the menu options button instead of a keypress

* Add mocks for newly added bridge functions and update editor history tests

* Remove unneeded icons

* Update color assets

* Update Main activity to use default layout and add the toolbar programmatically due to issues with views and appium

* Mobile E2E - Utils - Update to use the new menu button

* Update Add block button ID for iOS

* FIx navigation border bottom and icons insets

* Update undo/redo buttons for RTL

* Update undo/redo buttons when orientation changes, this is needed for Android's main host app activity

* Revert: Update undo/redo buttons when orientation changes, this is needed for Android's main host app activity

* Update changelog

* E2E helpers - Update getAddBlockButton
  • Loading branch information
Gerardo Pacheco authored Jul 11, 2023
1 parent 203789b commit c428fd4
Show file tree
Hide file tree
Showing 45 changed files with 733 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Platform, ScrollView, View } from 'react-native';
/**
* WordPress dependencies
*/
import { useCallback, useRef, useState } from '@wordpress/element';
import { useCallback, useRef, useState, useEffect } from '@wordpress/element';
import { compose, withPreferredColorScheme } from '@wordpress/compose';
import { withSelect, withDispatch } from '@wordpress/data';
import { withViewportMatch } from '@wordpress/viewport';
Expand All @@ -23,11 +23,15 @@ import {
media as imageIcon,
video as videoIcon,
gallery as galleryIcon,
undo as undoIcon,
redo as redoIcon,
} from '@wordpress/icons';
import { store as editorStore } from '@wordpress/editor';
import { createBlock } from '@wordpress/blocks';
import {
toggleUndoButton,
toggleRedoButton,
subscribeOnUndoPressed,
subscribeOnRedoPressed,
} from '@wordpress/react-native-bridge';

/**
* Internal dependencies
Expand All @@ -53,6 +57,24 @@ function HeaderToolbar( {
const wasNoContentSelected = useRef( noContentSelected );
const [ isInserterOpen, setIsInserterOpen ] = useState( false );

useEffect( () => {
const onUndoSubscription = subscribeOnUndoPressed( undo );
const onRedoSubscription = subscribeOnRedoPressed( redo );

return () => {
onUndoSubscription?.remove();
onRedoSubscription?.remove();
};
}, [ undo, redo ] );

useEffect( () => {
toggleUndoButton( ! hasUndo );
}, [ hasUndo ] );

useEffect( () => {
toggleRedoButton( ! hasRedo );
}, [ hasRedo ] );

const scrollViewRef = useRef( null );
const scrollToStart = () => {
// scrollview doesn't seem to automatically adjust to RTL on Android so, scroll to end when Android
Expand All @@ -64,34 +86,6 @@ function HeaderToolbar( {
}
};

const renderHistoryButtons = () => {
const buttons = [
/* TODO: replace with EditorHistoryRedo and EditorHistoryUndo. */
<ToolbarButton
key="undoButton"
title={ __( 'Undo' ) }
icon={ ! isRTL ? undoIcon : redoIcon }
isDisabled={ ! hasUndo }
onClick={ undo }
extraProps={ {
hint: __( 'Double tap to undo last change' ),
} }
/>,
<ToolbarButton
key="redoButton"
title={ __( 'Redo' ) }
icon={ ! isRTL ? redoIcon : undoIcon }
isDisabled={ ! hasRedo }
onClick={ redo }
extraProps={ {
hint: __( 'Double tap to redo last change' ),
} }
/>,
];

return isRTL ? buttons.reverse() : buttons;
};

const onInsertBlock = useCallback(
( blockType ) => () => {
insertBlock( createBlock( blockType ), undefined, undefined, true, {
Expand Down Expand Up @@ -195,9 +189,7 @@ function HeaderToolbar( {
useExpandedMode={ useExpandedMode }
onToggle={ onToggleInserter }
/>

{ noContentSelected && renderMediaButtons }
{ renderHistoryButtons() }
<BlockToolbar
anchorNodeRef={ anchorNodeRef.current }
onOpenBlockSettings={ onOpenBlockSettings }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,8 @@ void gutenbergDidRequestUnsupportedBlockFallback(ReplaceUnsupportedBlockCallback
void requestGotoCustomerSupportOptions();

void sendEventToHost(String eventName, ReadableMap properties);

void toggleUndoButton(boolean isDisabled);

void toggleRedoButton(boolean isDisabled);
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ public class RNReactNativeGutenbergBridgeModule extends ReactContextBaseJavaModu
private static final String EVENT_NAME_SHOW_NOTICE = "showNotice";
private static final String EVENT_NAME_SHOW_EDITOR_HELP = "showEditorHelp";

private static final String EVENT_NAME_ON_UNDO_PRESSED = "onUndoPressed";

private static final String EVENT_NAME_ON_REDO_PRESSED = "onRedoPressed";

private static final String MAP_KEY_UPDATE_HTML = "html";
private static final String MAP_KEY_UPDATE_TITLE = "title";
public static final String MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_NEW_ID = "newId";
Expand Down Expand Up @@ -192,6 +196,14 @@ public void showEditorHelp() {
emitToJS(EVENT_NAME_SHOW_EDITOR_HELP, null);
}

public void onUndoPressed() {
emitToJS(EVENT_NAME_ON_UNDO_PRESSED, null);
}

public void onRedoPressed() {
emitToJS(EVENT_NAME_ON_REDO_PRESSED, null);
}

@ReactMethod
public void addListener(String eventName) {
// Keep: Required for RN built in Event Emitter Calls.
Expand Down Expand Up @@ -497,6 +509,16 @@ public void sendEventToHost(final String eventName, final ReadableMap properties
mGutenbergBridgeJS2Parent.sendEventToHost(eventName, properties);
}

@ReactMethod
public void toggleUndoButton(final boolean isDisabled) {
mGutenbergBridgeJS2Parent.toggleUndoButton(isDisabled);
}

@ReactMethod
public void toggleRedoButton(final boolean isDisabled) {
mGutenbergBridgeJS2Parent.toggleRedoButton(isDisabled);
}

@ReactMethod
public void generateHapticFeedback() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ public class WPAndroidGlueCode {
private OnBlockTypeImpressionsEventListener mOnBlockTypeImpressionsEventListener;
private OnCustomerSupportOptionsListener mOnCustomerSupportOptionsListener;
private OnSendEventToHostListener mOnSendEventToHostListener;

private OnToggleUndoButtonListener mOnToggleUndoButtonListener;

private OnToggleRedoButtonListener mOnToggleRedoButtonListener;
private boolean mIsEditorMounted;

private String mContentHtml = "";
Expand Down Expand Up @@ -248,6 +252,14 @@ public interface OnSendEventToHostListener {
void onSendEventToHost(String eventName, Map<String, Object> properties);
}

public interface OnToggleUndoButtonListener {
void onToggleUndoButton(boolean isDisabled);
}

public interface OnToggleRedoButtonListener {
void onToggleRedoButton(boolean isDisabled);
}

public void mediaSelectionCancelled() {
mAppendsMultipleSelectedToSiblingBlocks = false;
}
Expand Down Expand Up @@ -573,6 +585,16 @@ public void requestGotoCustomerSupportOptions() {
public void sendEventToHost(String eventName, ReadableMap properties) {
mOnSendEventToHostListener.onSendEventToHost(eventName, properties.toHashMap());
}

@Override
public void toggleUndoButton(boolean isDisabled) {
mOnToggleUndoButtonListener.onToggleUndoButton(isDisabled);
}

@Override
public void toggleRedoButton(boolean isDisabled) {
mOnToggleRedoButtonListener.onToggleRedoButton(isDisabled);
}
}, mIsDarkMode);

return Arrays.asList(
Expand Down Expand Up @@ -666,6 +688,8 @@ public void attachToContainer(ViewGroup viewGroup,
OnBlockTypeImpressionsEventListener onBlockTypeImpressionsEventListener,
OnCustomerSupportOptionsListener onCustomerSupportOptionsListener,
OnSendEventToHostListener onSendEventToHostListener,
OnToggleUndoButtonListener onToggleUndoButtonListener,
OnToggleRedoButtonListener onToggleRedoButtonListener,
boolean isDarkMode) {
MutableContextWrapper contextWrapper = (MutableContextWrapper) mReactRootView.getContext();
contextWrapper.setBaseContext(viewGroup.getContext());
Expand All @@ -689,6 +713,8 @@ public void attachToContainer(ViewGroup viewGroup,
mOnBlockTypeImpressionsEventListener = onBlockTypeImpressionsEventListener;
mOnCustomerSupportOptionsListener = onCustomerSupportOptionsListener;
mOnSendEventToHostListener = onSendEventToHostListener;
mOnToggleUndoButtonListener = onToggleUndoButtonListener;
mOnToggleRedoButtonListener = onToggleRedoButtonListener;

sAddCookiesInterceptor.setOnAuthHeaderRequestedListener(onAuthHeaderRequestedListener);

Expand Down Expand Up @@ -819,6 +845,14 @@ public void showEditorHelp() {
mRnReactNativeGutenbergBridgePackage.getRNReactNativeGutenbergBridgeModule().showEditorHelp();
}

public void onUndoPressed() {
mRnReactNativeGutenbergBridgePackage.getRNReactNativeGutenbergBridgeModule().onUndoPressed();
}

public void onRedoPressed() {
mRnReactNativeGutenbergBridgePackage.getRNReactNativeGutenbergBridgeModule().onRedoPressed();
}

public void setTitle(String title) {
mTitleInitialized = true;
mTitle = title;
Expand Down
16 changes: 16 additions & 0 deletions packages/react-native-bridge/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ export function subscribeShowEditorHelp( callback ) {
return gutenbergBridgeEvents.addListener( 'showEditorHelp', callback );
}

export function subscribeOnUndoPressed( callback ) {
return gutenbergBridgeEvents.addListener( 'onUndoPressed', callback );
}

export function subscribeOnRedoPressed( callback ) {
return gutenbergBridgeEvents.addListener( 'onRedoPressed', callback );
}

/**
* Request media picker for the given media source.
*
Expand Down Expand Up @@ -466,4 +474,12 @@ export function generateHapticFeedback() {
RNReactNativeGutenbergBridge.generateHapticFeedback();
}

export function toggleUndoButton( isDisabled ) {
RNReactNativeGutenbergBridge.toggleUndoButton( isDisabled );
}

export function toggleRedoButton( isDisabled ) {
RNReactNativeGutenbergBridge.toggleRedoButton( isDisabled );
}

export default RNReactNativeGutenbergBridge;
8 changes: 8 additions & 0 deletions packages/react-native-bridge/ios/Gutenberg.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ public class Gutenberg: UIResponder {
public func showEditorHelp() {
bridgeModule.sendEventIfNeeded(.showEditorHelp, body: nil)
}

public func onUndoPressed() {
bridgeModule.sendEventIfNeeded(.onUndoPressed, body: nil)
}

public func onRedoPressed() {
bridgeModule.sendEventIfNeeded(.onRedoPressed, body: nil)
}

private func properties(from editorSettings: GutenbergEditorSettings?) -> [String : Any] {
var settingsUpdates = [String : Any]()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ public protocol GutenbergBridgeDelegate: AnyObject {

/// Tells the delegate the editor requested sending an event
func gutenbergDidRequestSendEventToHost(_ eventName: String, properties: [AnyHashable: Any])

func gutenbergDidRequestToggleUndoButton(_ isDisabled: Bool)

func gutenbergDidRequestToggleRedoButton(_ isDisabled: Bool)
}

// MARK: - Optional GutenbergBridgeDelegate methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,7 @@ @interface RCT_EXTERN_MODULE(RNReactNativeGutenbergBridge, NSObject)
RCT_EXTERN_METHOD(requestGotoCustomerSupportOptions)
RCT_EXTERN_METHOD(sendEventToHost:(NSString)eventName properties:(NSDictionary *)properties)
RCT_EXTERN_METHOD(generateHapticFeedback)
RCT_EXTERN_METHOD(toggleUndoButton:(BOOL)isDisabled)
RCT_EXTERN_METHOD(toggleRedoButton:(BOOL)isDisabled)

@end
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,16 @@ public class RNReactNativeGutenbergBridge: RCTEventEmitter {
func generateHapticFeedback() {
UISelectionFeedbackGenerator().selectionChanged()
}

@objc
func toggleUndoButton(_ isDisabled: Bool) {
self.delegate?.gutenbergDidRequestToggleUndoButton(isDisabled)
}

@objc
func toggleRedoButton(_ isDisabled: Bool) {
self.delegate?.gutenbergDidRequestToggleRedoButton(isDisabled)
}
}

// MARK: - RCTBridgeModule delegate
Expand Down Expand Up @@ -438,6 +448,8 @@ extension RNReactNativeGutenbergBridge {
case showNotice
case mediaSave
case showEditorHelp
case onUndoPressed
case onRedoPressed
}

public override func supportedEvents() -> [String]! {
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ For each user feature we should also add a importance categorization label to i
## Unreleased
- [**] Add media inserter buttons to editor toolbar [#51827]
- [**] Update native BlockOutline component styles to remove blue border from blocks [#51222]
- [**] Move the undo/redo buttons to the navigation bar [#51766]

## 1.99.0
- [*] Rename "Reusable blocks" to "Synced patterns", aligning with the web editor. [#51704]
Expand Down
19 changes: 11 additions & 8 deletions packages/react-native-editor/__device-tests__/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -476,29 +476,32 @@ const dragAndDropAfterElement = async ( driver, element, nextElement ) => {

const toggleHtmlMode = async ( driver, toggleOn ) => {
if ( isAndroid() ) {
// Hit the "Menu" key.
await driver.pressKeycode( 82 );
const moreOptionsButton = await driver.elementByAccessibilityId(
'More options'
);
await moreOptionsButton.click();

const showHtmlButtonXpath =
'/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.ListView/android.widget.TextView[9]';

await clickIfClickable( driver, showHtmlButtonXpath );
} else if ( toggleOn ) {
await clickIfClickable(
driver,
'//XCUIElementTypeButton[@name="..."]'
const moreOptionsButton = await driver.elementByAccessibilityId(
'editor-menu-button'
);
await moreOptionsButton.click();

await clickIfClickable(
driver,
'//XCUIElementTypeButton[@name="Switch to HTML"]'
);
} else {
// This is to wait for the clipboard paste notification to disappear, currently it overlaps with the menu button
await driver.sleep( 3000 );
await clickIfClickable(
driver,
'//XCUIElementTypeButton[@name="..."]'
const moreOptionsButton = await driver.elementByAccessibilityId(
'editor-menu-button'
);
await moreOptionsButton.click();
await clickIfClickable(
driver,
'//XCUIElementTypeButton[@name="Switch To Visual"]'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const {

const ADD_BLOCK_ID = isAndroid()
? 'Add block, Double tap to add a block'
: 'Add block';
: 'add-block-button';

const initializeEditorPage = async () => {
const driver = await setupDriver();
Expand Down Expand Up @@ -71,11 +71,11 @@ class EditorPage {
return await this.driver.hasElementByAccessibilityId( 'block-list' );
}

async getAddBlockButton( options = { timeout: 3000 } ) {
return await this.waitForElementToBeDisplayedById(
ADD_BLOCK_ID,
options.timeout
async getAddBlockButton() {
const elements = await this.driver.elementsByAccessibilityId(
ADD_BLOCK_ID
);
return elements[ 0 ];
}

// ===============================
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-editor/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ dependencies {

implementation "org.wordpress-mobile.gutenberg-mobile:react-native-bridge"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "com.google.android.material:material:1.9.0"
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:${extractPackageVersion(packageJson, 'react-native', 'dependencies')}"

Expand Down
Loading

0 comments on commit c428fd4

Please sign in to comment.