Skip to content

Commit

Permalink
Refactor and migrate FlutterUndoManagerPlugin to ARC
Browse files Browse the repository at this point in the history
  • Loading branch information
jmagman committed Apr 18, 2024
1 parent aa6f741 commit 231e00e
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 81 deletions.
6 changes: 3 additions & 3 deletions shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ source_set("flutter_framework_source_arc") {
"framework/Source/FlutterTextureRegistryRelay.mm",
"framework/Source/FlutterUIPressProxy.h",
"framework/Source/FlutterUIPressProxy.mm",
"framework/Source/FlutterUndoManagerDelegate.h",
"framework/Source/FlutterUndoManagerPlugin.h",
"framework/Source/FlutterUndoManagerPlugin.mm",
"framework/Source/KeyCodeMap.g.mm",
"framework/Source/KeyCodeMap_Internal.h",
"framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h",
Expand Down Expand Up @@ -157,9 +160,6 @@ source_set("flutter_framework_source") {
"framework/Source/FlutterPluginAppLifeCycleDelegate.mm",
"framework/Source/FlutterSemanticsScrollView.h",
"framework/Source/FlutterSemanticsScrollView.mm",
"framework/Source/FlutterUndoManagerDelegate.h",
"framework/Source/FlutterUndoManagerPlugin.h",
"framework/Source/FlutterUndoManagerPlugin.mm",
"framework/Source/FlutterView.h",
"framework/Source/FlutterView.mm",
"framework/Source/FlutterViewController.mm",
Expand Down
2 changes: 0 additions & 2 deletions shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,6 @@ - (void)setViewController:(FlutterViewController*)viewController {
[self maybeSetupPlatformViewChannels];
[self updateDisplays];
_textInputPlugin.get().viewController = viewController;
_undoManagerPlugin.get().viewController = viewController;

if (viewController) {
__block FlutterEngine* blockSelf = self;
Expand Down Expand Up @@ -465,7 +464,6 @@ - (void)setFlutterViewControllerWillDeallocObserver:(id<NSObject>)observer {
- (void)notifyViewControllerDeallocated {
[[self lifecycleChannel] sendMessage:@"AppLifecycleState.detached"];
_textInputPlugin.get().viewController = nil;
_undoManagerPlugin.get().viewController = nil;
if (!_allowHeadlessExecution) {
[self destroyContext];
} else if (_shell) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#import <Foundation/Foundation.h>

#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"

NS_ASSUME_NONNULL_BEGIN

typedef NS_ENUM(NSInteger, FlutterUndoRedoDirection) {
Expand All @@ -19,8 +21,14 @@ typedef NS_ENUM(NSInteger, FlutterUndoRedoDirection) {
@class FlutterUndoManagerPlugin;

@protocol FlutterUndoManagerDelegate <NSObject>

@property(nonatomic, weak) UIResponder* viewController;

- (FlutterTextInputPlugin*)textInputPlugin;

- (void)flutterUndoManagerPlugin:(FlutterUndoManagerPlugin*)undoManagerPlugin
handleUndoWithDirection:(FlutterUndoRedoDirection)direction;

@end
NS_ASSUME_NONNULL_END

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@

#import <UIKit/UIKit.h>

#import "flutter/fml/memory/weak_ptr.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h"

@interface FlutterUndoManagerPlugin : NSObject

@property(nonatomic, assign) FlutterViewController* viewController;

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@
// found in the LICENSE file.

#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

#include "flutter/fml/logging.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"

#pragma mark - UndoManager channel method names.
static NSString* const kSetUndoStateMethod = @"UndoManager.setUndoState";
Expand All @@ -17,15 +13,16 @@
static NSString* const kCanUndo = @"canUndo";
static NSString* const kCanRedo = @"canRedo";

@implementation FlutterUndoManagerPlugin {
id<FlutterUndoManagerDelegate> _undoManagerDelegate;
}
@interface FlutterUndoManagerPlugin ()
@property(nonatomic, weak, readonly) id<FlutterUndoManagerDelegate> undoManagerDelegate;
@end

@implementation FlutterUndoManagerPlugin

- (instancetype)initWithDelegate:(id<FlutterUndoManagerDelegate>)undoManagerDelegate {
self = [super init];

if (self) {
// `_undoManagerDelegate` is a weak reference because it should retain FlutterUndoManagerPlugin.
_undoManagerDelegate = undoManagerDelegate;
}

Expand All @@ -34,7 +31,6 @@ - (instancetype)initWithDelegate:(id<FlutterUndoManagerDelegate>)undoManagerDele

- (void)dealloc {
[self resetUndoManager];
[super dealloc];
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
Expand All @@ -49,45 +45,47 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
}

- (NSUndoManager*)undoManager {
return _viewController.undoManager;
return self.undoManagerDelegate.viewController.undoManager;
}

- (void)resetUndoManager API_AVAILABLE(ios(9.0)) {
[[self undoManager] removeAllActionsWithTarget:self];
}

- (void)registerUndoWithDirection:(FlutterUndoRedoDirection)direction API_AVAILABLE(ios(9.0)) {
[[self undoManager] beginUndoGrouping];
[[self undoManager] registerUndoWithTarget:self
handler:^(id target) {
// Register undo with opposite direction.
FlutterUndoRedoDirection newDirection =
(direction == FlutterUndoRedoDirectionRedo)
? FlutterUndoRedoDirectionUndo
: FlutterUndoRedoDirectionRedo;
[target registerUndoWithDirection:newDirection];
// Invoke method on delegate.
[_undoManagerDelegate flutterUndoManagerPlugin:self
handleUndoWithDirection:direction];
}];
[[self undoManager] endUndoGrouping];
NSUndoManager* undoManager = [self undoManager];
[undoManager beginUndoGrouping];
[undoManager registerUndoWithTarget:self
handler:^(FlutterUndoManagerPlugin* target) {
// Register undo with opposite direction.
FlutterUndoRedoDirection newDirection =
(direction == FlutterUndoRedoDirectionRedo)
? FlutterUndoRedoDirectionUndo
: FlutterUndoRedoDirectionRedo;
[target registerUndoWithDirection:newDirection];
// Invoke method on delegate.
[target.undoManagerDelegate flutterUndoManagerPlugin:target
handleUndoWithDirection:direction];
}];
[undoManager endUndoGrouping];
}

- (void)registerRedo API_AVAILABLE(ios(9.0)) {
[[self undoManager] beginUndoGrouping];
[[self undoManager]
registerUndoWithTarget:self
handler:^(id target) {
// Register undo with opposite direction.
[target registerUndoWithDirection:FlutterUndoRedoDirectionRedo];
}];
[[self undoManager] endUndoGrouping];
[[self undoManager] undo];
NSUndoManager* undoManager = [self undoManager];
[undoManager beginUndoGrouping];
[undoManager registerUndoWithTarget:self
handler:^(id target) {
// Register undo with opposite direction.
[target registerUndoWithDirection:FlutterUndoRedoDirectionRedo];
}];
[undoManager endUndoGrouping];
[undoManager undo];
}

- (void)setUndoState:(NSDictionary*)dictionary API_AVAILABLE(ios(9.0)) {
BOOL groupsByEvent = [self undoManager].groupsByEvent;
[self undoManager].groupsByEvent = NO;
NSUndoManager* undoManager = [self undoManager];
BOOL groupsByEvent = undoManager.groupsByEvent;
undoManager.groupsByEvent = NO;
BOOL canUndo = [dictionary[kCanUndo] boolValue];
BOOL canRedo = [dictionary[kCanRedo] boolValue];

Expand All @@ -99,16 +97,15 @@ - (void)setUndoState:(NSDictionary*)dictionary API_AVAILABLE(ios(9.0)) {
if (canRedo) {
[self registerRedo];
}

if (_viewController.engine.textInputPlugin.textInputView != nil) {
UIView<UITextInput>* textInputView = [self.undoManagerDelegate.textInputPlugin textInputView];
if (textInputView != nil) {
// This is needed to notify the iPadOS keyboard that it needs to update the
// state of the UIBarButtons. Otherwise, the state changes to NSUndoManager
// will not show up until the next keystroke (or other trigger).
UITextInputAssistantItem* assistantItem =
_viewController.engine.textInputPlugin.textInputView.inputAssistantItem;
UITextInputAssistantItem* assistantItem = textInputView.inputAssistantItem;
assistantItem.leadingBarButtonGroups = assistantItem.leadingBarButtonGroups;
}
[self undoManager].groupsByEvent = groupsByEvent;
undoManager.groupsByEvent = groupsByEvent;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,48 @@
#import <XCTest/XCTest.h>

#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"

FLUTTER_ASSERT_ARC

@interface FlutterEngine ()
- (nonnull FlutterUndoManagerPlugin*)undoManagerPlugin;
- (nonnull FlutterTextInputPlugin*)textInputPlugin;
@interface FlutterUndoManagerDelegateForTest : NSObject <FlutterUndoManagerDelegate>
@property(nonatomic, weak) UIResponder* viewController;
@property(nonatomic) FlutterTextInputPlugin* textInputPlugin;
@end

@interface FlutterUndoManagerPluginForTest : FlutterUndoManagerPlugin
@property(nonatomic, assign) NSUndoManager* undoManager;
@end
@implementation FlutterUndoManagerDelegateForTest

@implementation FlutterUndoManagerPluginForTest {
- (void)flutterUndoManagerPlugin:(FlutterUndoManagerPlugin*)undoManagerPlugin
handleUndoWithDirection:(FlutterUndoRedoDirection)direction {
}
@end

@interface FlutterUndoManagerPluginTest : XCTestCase
@property(nonatomic, strong) id engine;
@property(nonatomic, strong) FlutterUndoManagerPluginForTest* undoManagerPlugin;
@property(nonatomic, strong) FlutterViewController* viewController;
@property(nonatomic, strong) NSUndoManager* undoManager;
@property(nonatomic) id undoManagerDelegate;
@property(nonatomic) FlutterUndoManagerPlugin* undoManagerPlugin;
@property(nonatomic) UIResponder* viewController;
@property(nonatomic) NSUndoManager* undoManager;
@end

@implementation FlutterUndoManagerPluginTest {
}
@implementation FlutterUndoManagerPluginTest

- (void)setUp {
[super setUp];
self.engine = OCMClassMock([FlutterEngine class]);

self.undoManagerPlugin = [[FlutterUndoManagerPluginForTest alloc] initWithDelegate:self.engine];
self.undoManagerDelegate = OCMClassMock([FlutterUndoManagerDelegateForTest class]);

self.viewController = [[FlutterViewController alloc] init];
self.undoManagerPlugin.viewController = self.viewController;
self.undoManagerPlugin =
[[FlutterUndoManagerPlugin alloc] initWithDelegate:self.undoManagerDelegate];

self.undoManager = OCMClassMock([NSUndoManager class]);
self.undoManagerPlugin.undoManager = self.undoManager;

self.viewController = OCMClassMock([UIResponder class]);
OCMStub([self.viewController undoManager]).andReturn(self.undoManager);
OCMStub([self.undoManagerDelegate viewController]).andReturn(self.viewController);
}

- (void)tearDown {
[self.undoManager removeAllActionsWithTarget:self.undoManagerPlugin];
self.engine = nil;
self.undoManagerDelegate = nil;
self.viewController = nil;
self.undoManager = nil;
[super tearDown];
Expand All @@ -75,14 +72,14 @@ - (void)testSetUndoState {
removeAllActionsCount++;
});
__block int delegateUndoCount = 0;
OCMStub([self.engine flutterUndoManagerPlugin:[OCMArg any]
handleUndoWithDirection:FlutterUndoRedoDirectionUndo])
OCMStub([self.undoManagerDelegate flutterUndoManagerPlugin:[OCMArg any]
handleUndoWithDirection:FlutterUndoRedoDirectionUndo])
.andDo(^(NSInvocation* invocation) {
delegateUndoCount++;
});
__block int delegateRedoCount = 0;
OCMStub([self.engine flutterUndoManagerPlugin:[OCMArg any]
handleUndoWithDirection:FlutterUndoRedoDirectionRedo])
OCMStub([self.undoManagerDelegate flutterUndoManagerPlugin:[OCMArg any]
handleUndoWithDirection:FlutterUndoRedoDirectionRedo])
.andDo(^(NSInvocation* invocation) {
delegateRedoCount++;
});
Expand Down Expand Up @@ -143,14 +140,10 @@ - (void)testSetUndoState {

- (void)testSetUndoStateDoesInteractWithInputDelegate {
// Regression test for https://github.com/flutter/flutter/issues/133424
FlutterViewController* viewController = OCMPartialMock(self.viewController);
self.undoManagerPlugin.viewController = self.viewController;

FlutterTextInputPlugin* textInputPlugin = OCMClassMock([FlutterTextInputPlugin class]);
FlutterTextInputView* textInputView = OCMClassMock([FlutterTextInputView class]);

OCMStub([viewController engine]).andReturn(self.engine);
OCMStub([self.engine textInputPlugin]).andReturn(textInputPlugin);
OCMStub([self.undoManagerDelegate textInputPlugin]).andReturn(textInputPlugin);
OCMStub([textInputPlugin textInputView]).andReturn(textInputView);

FlutterMethodCall* setUndoStateCall =
Expand Down

0 comments on commit 231e00e

Please sign in to comment.