blob: 73ce3ad5f7a8500c2d1b439c02cf401b00e1c238 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import '../../services.dart';
/// The direction in which an undo action should be performed, whether undo or redo.
enum UndoDirection {
/// Perform an undo action.
undo,
/// Perform a redo action.
redo,
}
/// A low-level interface to the system's undo manager.
///
/// To receive events from the system undo manager, create an
/// [UndoManagerClient] and set it as the [client] on [UndoManager].
///
/// The [setUndoState] method can be used to update the system's undo manager
/// using the `canUndo` and `canRedo` parameters.
///
/// When the system undo or redo button is tapped, the current
/// [UndoManagerClient] will receive [UndoManagerClient.handlePlatformUndo]
/// with an [UndoDirection] representing whether the event is "undo" or "redo".
///
/// Currently, only iOS has an UndoManagerPlugin implemented on the engine side.
/// On iOS, this can be used to listen to the keyboard undo/redo buttons and the
/// undo/redo gestures.
///
/// See also:
///
/// * [NSUndoManager](https://developer.apple.com/documentation/foundation/nsundomanager)
class UndoManager {
UndoManager._() {
_channel = SystemChannels.undoManager;
_channel.setMethodCallHandler(_handleUndoManagerInvocation);
}
/// Set the [MethodChannel] used to communicate with the system's undo manager.
///
/// This is only meant for testing within the Flutter SDK. Changing this
/// will break the ability to set the undo status or receive undo and redo
/// events from the system. This has no effect if asserts are disabled.
@visibleForTesting
static void setChannel(MethodChannel newChannel) {
assert(() {
_instance._channel = newChannel..setMethodCallHandler(_instance._handleUndoManagerInvocation);
return true;
}());
}
static final UndoManager _instance = UndoManager._();
/// Receive undo and redo events from the system's [UndoManager].
///
/// Setting the [client] will cause [UndoManagerClient.handlePlatformUndo]
/// to be called when a system undo or redo is triggered, such as by tapping
/// the undo/redo keyboard buttons or using the 3-finger swipe gestures.
static set client(UndoManagerClient? client) {
_instance._currentClient = client;
}
/// Return the current [UndoManagerClient].
static UndoManagerClient? get client => _instance._currentClient;
/// Set the current state of the system UndoManager. [canUndo] and [canRedo]
/// control the respective "undo" and "redo" buttons of the system UndoManager.
static void setUndoState({bool canUndo = false, bool canRedo = false}) {
_instance._setUndoState(canUndo: canUndo, canRedo: canRedo);
}
late MethodChannel _channel;
UndoManagerClient? _currentClient;
Future<dynamic> _handleUndoManagerInvocation(MethodCall methodCall) async {
final String method = methodCall.method;
final List<dynamic> args = methodCall.arguments as List<dynamic>;
if (method == 'UndoManagerClient.handleUndo') {
assert(_currentClient != null, 'There must be a current UndoManagerClient.');
_currentClient!.handlePlatformUndo(_toUndoDirection(args[0] as String));
return;
}
throw MissingPluginException();
}
void _setUndoState({bool canUndo = false, bool canRedo = false}) {
_channel.invokeMethod<void>('UndoManager.setUndoState', <String, bool>{
'canUndo': canUndo,
'canRedo': canRedo,
});
}
UndoDirection _toUndoDirection(String direction) {
return switch (direction) {
'undo' => UndoDirection.undo,
'redo' => UndoDirection.redo,
_ =>
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('Unknown undo direction: $direction'),
]),
};
}
}
/// An interface to receive events from a native UndoManager.
mixin UndoManagerClient {
/// Requests that the client perform an undo or redo operation.
///
/// Currently only used on iOS 9+ when the undo or redo methods are invoked
/// by the platform. For example, when using three-finger swipe gestures,
/// the iPad keyboard, or voice control.
void handlePlatformUndo(UndoDirection direction);
/// Reverts the value on the stack to the previous value.
void undo();
/// Updates the value on the stack to the next value.
void redo();
/// Will be true if there are past values on the stack.
bool get canUndo;
/// Will be true if there are future values on the stack.
bool get canRedo;
}