Commit 88cf68c8 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Migrate Clipboard to platform messages (#6367)

The platform plugin now supports clipboard operations.
parent 50ebcd1d
...@@ -50,7 +50,7 @@ class _TextSelectionToolbar extends StatelessWidget { ...@@ -50,7 +50,7 @@ class _TextSelectionToolbar extends StatelessWidget {
} }
void _handleCut() { void _handleCut() {
Clipboard.setClipboardData(new ClipboardData()..text = value.selection.textInside(value.text)); Clipboard.setData(new ClipboardData(text: value.selection.textInside(value.text)));
delegate.inputValue = new InputValue( delegate.inputValue = new InputValue(
text: value.selection.textBefore(value.text) + value.selection.textAfter(value.text), text: value.selection.textBefore(value.text) + value.selection.textAfter(value.text),
selection: new TextSelection.collapsed(offset: value.selection.start) selection: new TextSelection.collapsed(offset: value.selection.start)
...@@ -59,7 +59,7 @@ class _TextSelectionToolbar extends StatelessWidget { ...@@ -59,7 +59,7 @@ class _TextSelectionToolbar extends StatelessWidget {
} }
void _handleCopy() { void _handleCopy() {
Clipboard.setClipboardData(new ClipboardData()..text = value.selection.textInside(value.text)); Clipboard.setData(new ClipboardData(text: value.selection.textInside(value.text)));
delegate.inputValue = new InputValue( delegate.inputValue = new InputValue(
text: value.text, text: value.text,
selection: new TextSelection.collapsed(offset: value.selection.end) selection: new TextSelection.collapsed(offset: value.selection.end)
...@@ -69,11 +69,11 @@ class _TextSelectionToolbar extends StatelessWidget { ...@@ -69,11 +69,11 @@ class _TextSelectionToolbar extends StatelessWidget {
Future<Null> _handlePaste() async { Future<Null> _handlePaste() async {
InputValue value = this.value; // Snapshot the input before using `await`. InputValue value = this.value; // Snapshot the input before using `await`.
ClipboardData clip = await Clipboard.getClipboardData(Clipboard.kTextPlain); ClipboardData data = await Clipboard.getData(Clipboard.kTextPlain);
if (clip != null) { if (data != null) {
delegate.inputValue = new InputValue( delegate.inputValue = new InputValue(
text: value.selection.textBefore(value.text) + clip.text + value.selection.textAfter(value.text), text: value.selection.textBefore(value.text) + data.text + value.selection.textAfter(value.text),
selection: new TextSelection.collapsed(offset: value.selection.start + clip.text.length) selection: new TextSelection.collapsed(offset: value.selection.start + data.text.length)
); );
} }
delegate.hideToolbar(); delegate.hideToolbar();
......
...@@ -4,38 +4,48 @@ ...@@ -4,38 +4,48 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_services/editing.dart' as mojom; import 'platform_messages.dart';
import 'shell.dart'; /// Data stored on the system clip board.
///
export 'package:flutter_services/editing.dart' show ClipboardData; /// The system clip board can contain data of various media types. This data
/// structure currently supports only plain text data in the [text] property.
mojom.ClipboardProxy _initClipboardProxy() { class ClipboardData {
return shell.connectToApplicationService('mojo:clipboard', mojom.Clipboard.connectToService); /// Creates data for the system clipboard.
const ClipboardData({ this.text });
/// Plain text data on the clip board.
final String text;
} }
final mojom.ClipboardProxy _clipboardProxy = _initClipboardProxy(); /// An interface to the system's clipboard.
/// An interface to the system's clipboard. Wraps the mojo interface.
class Clipboard { class Clipboard {
/// Constants for common [getClipboardData] [format] types. /// Constants for common [getData] [format] types.
static final String kTextPlain = 'text/plain'; static final String kTextPlain = 'text/plain';
Clipboard._(); Clipboard._();
/// Stores the given clipboard data on the clipboard. /// Stores the given clipboard data on the clipboard.
static void setClipboardData(mojom.ClipboardData clip) { static Future<Null> setData(ClipboardData data) async {
_clipboardProxy.setClipboardData(clip); await PlatformMessages.sendJSON('flutter/platform', <String, dynamic>{
'method': 'Clipboard.setData',
'args': <Map<String, dynamic>>[<String, dynamic>{
'text': data.text,
}],
});
} }
/// Retrieves data from the clipboard that matches the given format. /// Retrieves data from the clipboard that matches the given format.
/// ///
/// * `format` is a media type, such as `text/plain`. /// * `format` is a media type, such as `text/plain`.
static Future<mojom.ClipboardData> getClipboardData(String format) { static Future<ClipboardData> getData(String format) async {
Completer<mojom.ClipboardData> completer = new Completer<mojom.ClipboardData>(); Map<String, dynamic> result =
_clipboardProxy.getClipboardData(format, (mojom.ClipboardData clip) { await PlatformMessages.sendJSON('flutter/platform', <String, dynamic>{
completer.complete(clip); 'method': 'Clipboard.getData',
'args': <String>[format],
}); });
return completer.future; if (result == null)
return null;
return new ClipboardData(text: result['text']);
} }
} }
...@@ -28,29 +28,35 @@ dynamic _decodeJSON(String message) { ...@@ -28,29 +28,35 @@ dynamic _decodeJSON(String message) {
return message != null ? JSON.decode(message) : null; return message != null ? JSON.decode(message) : null;
} }
void _sendString(String name, String message, void callback(String reply)) {
Uint8List encoded = UTF8.encoder.convert(message);
ui.window.sendPlatformMessage(name, encoded.buffer.asByteData(), (ByteData reply) {
try {
callback(_decodeUTF8(reply));
} catch (exception, stack) {
FlutterError.reportError(new FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: 'during a platform message response callback',
));
}
});
}
typedef Future<ByteData> _PlatformMessageHandler(ByteData message); typedef Future<ByteData> _PlatformMessageHandler(ByteData message);
/// Sends message to and receives messages from the underlying platform. /// Sends message to and receives messages from the underlying platform.
class PlatformMessages { class PlatformMessages {
/// Handlers for incoming platform messages.
static final Map<String, _PlatformMessageHandler> _handlers = static final Map<String, _PlatformMessageHandler> _handlers =
<String, _PlatformMessageHandler>{}; <String, _PlatformMessageHandler>{};
/// Mock handlers that intercept and respond to outgoing messages.
static final Map<String, _PlatformMessageHandler> _mockHandlers =
<String, _PlatformMessageHandler>{};
static Future<ByteData> _sendPlatformMessage(String name, ByteData message) {
final Completer<ByteData> completer = new Completer<ByteData>();
ui.window.sendPlatformMessage(name, message, (ByteData reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(new FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: 'during a platform message response callback',
));
}
});
return completer.future;
}
/// Calls the handler registered for the given name. /// Calls the handler registered for the given name.
/// ///
/// Typically called by [ServicesBinding] to handle platform messages received /// Typically called by [ServicesBinding] to handle platform messages received
...@@ -77,22 +83,22 @@ class PlatformMessages { ...@@ -77,22 +83,22 @@ class PlatformMessages {
} }
} }
/// Send a binary message to the host application.
static Future<ByteData> sendBinary(String name, ByteData message) {
final _PlatformMessageHandler handler = _mockHandlers[name];
if (handler != null)
return handler(message);
return _sendPlatformMessage(name, message);
}
/// Send a string message to the host application. /// Send a string message to the host application.
static Future<String> sendString(String name, String message) { static Future<String> sendString(String name, String message) async {
Completer<String> completer = new Completer<String>(); return _decodeUTF8(await sendBinary(name, _encodeUTF8(message)));
_sendString(name, message, (String reply) {
completer.complete(reply);
});
return completer.future;
} }
/// Sends a JSON-encoded message to the host application and JSON-decodes the response. /// Sends a JSON-encoded message to the host application and JSON-decodes the response.
static Future<dynamic> sendJSON(String name, dynamic json) { static Future<dynamic> sendJSON(String name, dynamic json) async {
Completer<dynamic> completer = new Completer<dynamic>(); return _decodeJSON(await sendString(name, _encodeJSON(json)));
_sendString(name, JSON.encode(json), (String reply) {
completer.complete(_decodeJSON(reply));
});
return completer.future;
} }
/// Set a callback for receiving binary messages from the platform. /// Set a callback for receiving binary messages from the platform.
...@@ -123,4 +129,35 @@ class PlatformMessages { ...@@ -123,4 +129,35 @@ class PlatformMessages {
return _encodeJSON(await handler(_decodeJSON(message))); return _encodeJSON(await handler(_decodeJSON(message)));
}); });
} }
/// Sets a message handler that intercepts outgoing messages in binary form.
///
/// The given callback will replace the currently registered callback (if any).
/// To remove the mock handler, pass `null` as the `handler` argument.
static void setMockBinaryMessageHandler(String name, Future<ByteData> handler(ByteData message)) {
if (handler == null)
_mockHandlers.remove(handler);
else
_mockHandlers[name] = handler;
}
/// Sets a message handler that intercepts outgoing messages in string form.
///
/// The given callback will replace the currently registered callback (if any).
/// To remove the mock handler, pass `null` as the `handler` argument.
static void setMockStringMessageHandler(String name, Future<String> handler(String message)) {
setMockBinaryMessageHandler(name, (ByteData message) async {
return _encodeUTF8(await handler(_decodeUTF8(message)));
});
}
/// Sets a message handler that intercepts outgoing messages in JSON form.
///
/// The given callback will replace the currently registered callback (if any).
/// To remove the mock handler, pass `null` as the `handler` argument.
static void setMockJSONMessageHandler(String name, Future<dynamic> handler(dynamic message)) {
setMockStringMessageHandler(name, (String message) async {
return _encodeJSON(await handler(_decodeJSON(message)));
});
}
} }
...@@ -7,6 +7,7 @@ import 'dart:async'; ...@@ -7,6 +7,7 @@ import 'dart:async';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_services/editing.dart' as mojom; import 'package:flutter_services/editing.dart' as mojom;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
...@@ -30,29 +31,30 @@ class MockKeyboard extends mojom.KeyboardProxy { ...@@ -30,29 +31,30 @@ class MockKeyboard extends mojom.KeyboardProxy {
void setEditingState(mojom.EditingState state) {} void setEditingState(mojom.EditingState state) {}
} }
class MockClipboard extends mojom.ClipboardProxy { class MockClipboard {
MockClipboard() : super.unbound(); Object _clipboardData = <String, dynamic>{
'text': null
mojom.ClipboardData _clip; };
@override Future<dynamic> handleJSONMessage(dynamic json) async {
void setClipboardData(mojom.ClipboardData clip) { final String method = json['method'];
_clip = clip; final List<dynamic> args= json['args'];
} switch (method) {
case 'Clipboard.getData':
@override return _clipboardData;
void getClipboardData(String format, void callback(mojom.ClipboardData clip)) { case 'Clipboard.setData':
scheduleMicrotask(() { _clipboardData = args[0];
callback(_clip); break;
}); }
} }
} }
void main() { void main() {
MockKeyboard mockKeyboard = new MockKeyboard(); MockKeyboard mockKeyboard = new MockKeyboard();
serviceMocker.registerMockService(mockKeyboard); serviceMocker.registerMockService(mockKeyboard);
MockClipboard mockClipboard = new MockClipboard(); MockClipboard mockClipboard = new MockClipboard();
serviceMocker.registerMockService(mockClipboard); PlatformMessages.setMockJSONMessageHandler('flutter/platform', mockClipboard.handleJSONMessage);
const String kThreeLines = const String kThreeLines =
'First line of text is here abcdef ghijkl mnopqrst. ' + 'First line of text is here abcdef ghijkl mnopqrst. ' +
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment