forked from flutter/packages
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for iOS UndoManager (#98294)
Add support for iOS UndoManager
- Loading branch information
Showing
14 changed files
with
1,429 additions
and
347 deletions.
There are no files selected for viewing
81 changes: 81 additions & 0 deletions
81
examples/api/lib/widgets/undo_history/undo_history_controller.0.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// 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. | ||
|
||
// Flutter code sample for UndoHistoryController. | ||
|
||
import 'package:flutter/material.dart'; | ||
|
||
void main() { | ||
runApp(const MyApp()); | ||
} | ||
|
||
class MyApp extends StatelessWidget { | ||
const MyApp({super.key}); | ||
|
||
static const String _title = 'Flutter Code Sample'; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return const MaterialApp( | ||
title: _title, | ||
home: MyHomePage(), | ||
); | ||
} | ||
} | ||
|
||
class MyHomePage extends StatefulWidget { | ||
const MyHomePage({super.key}); | ||
|
||
@override | ||
State<MyHomePage> createState() => _MyHomePageState(); | ||
} | ||
|
||
class _MyHomePageState extends State<MyHomePage> { | ||
final TextEditingController _controller = TextEditingController(); | ||
final FocusNode _focusNode = FocusNode(); | ||
final UndoHistoryController _undoController = UndoHistoryController(); | ||
|
||
TextStyle? get enabledStyle => Theme.of(context).textTheme.bodyMedium; | ||
TextStyle? get disabledStyle => Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Scaffold( | ||
body: Center( | ||
child: Column( | ||
mainAxisAlignment: MainAxisAlignment.center, | ||
children: <Widget>[ | ||
TextField( | ||
maxLines: 4, | ||
controller: _controller, | ||
focusNode: _focusNode, | ||
undoController: _undoController, | ||
), | ||
ValueListenableBuilder<UndoHistoryValue>( | ||
valueListenable: _undoController, | ||
builder: (BuildContext context, UndoHistoryValue value, Widget? child) { | ||
return Row( | ||
children: <Widget>[ | ||
TextButton( | ||
child: Text('Undo', style: value.canUndo ? enabledStyle : disabledStyle), | ||
onPressed: () { | ||
_undoController.undo(); | ||
}, | ||
), | ||
TextButton( | ||
child: Text('Redo', style: value.canRedo ? enabledStyle : disabledStyle), | ||
onPressed: () { | ||
_undoController.redo(); | ||
}, | ||
), | ||
], | ||
); | ||
}, | ||
), | ||
], | ||
), | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
// 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) { | ||
switch (direction) { | ||
case 'undo': | ||
return UndoDirection.undo; | ||
case 'redo': | ||
return 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; | ||
} |
Oops, something went wrong.