diff --git a/example/flows/text_flow.yaml b/example/flows/text_flow.yaml index a29043a6..bb579b32 100644 --- a/example/flows/text_flow.yaml +++ b/example/flows/text_flow.yaml @@ -8,8 +8,10 @@ description: Testing the text page - expectVisible: "Text" - expectVisible: "Result: " - pressOn: "Enter text" -- inputText: "Testing the text page" -- expectVisible: "Result: Testing the text page" +# - inputText: "Testing the text page" +# - expectVisible: "Result: Testing the text page" +- pressKey: "backspace" +- expectVisible: "FAILING ON PURPOSE TO TEST BACKSPACE KEY" # Return to the app page - pressOn: "Back" - expectEnvironmentText: diff --git a/example/lib/text/view/text_page.dart b/example/lib/text/view/text_page.dart index 86dfe80f..20c810df 100644 --- a/example/lib/text/view/text_page.dart +++ b/example/lib/text/view/text_page.dart @@ -29,11 +29,18 @@ class TextView extends StatefulWidget { class _TextViewState extends State { late TextEditingController _controller; + late FocusNode focusNode; @override void initState() { super.initState(); _controller = TextEditingController(); + focusNode = FocusNode( + onKey: (node, event) { + print(event); + return KeyEventResult.handled; + }, + ); _controller.addListener(_onChange); } @@ -59,6 +66,7 @@ class _TextViewState extends State { child: Column( children: [ TextField( + focusNode: focusNode, controller: _controller, decoration: const InputDecoration(labelText: 'Enter text'), ), diff --git a/packages/fluttium/lib/src/actions/press_key.dart b/packages/fluttium/lib/src/actions/press_key.dart new file mode 100644 index 00000000..4a90fcee --- /dev/null +++ b/packages/fluttium/lib/src/actions/press_key.dart @@ -0,0 +1,53 @@ +import 'package:flutter/services.dart'; +import 'package:fluttium/fluttium.dart'; + +/// {@template press_key} +/// TODO: docs +/// {@endtemplate} +class PressKey extends Action { + /// {@macro press_key} + const PressKey({ + required this.key, + this.downFor = 1, + }); + + /// The key that is being pressed. + final String key; + + /// For how long the key will be down. + final int downFor; + + @override + Future execute(Tester tester) async { + final logicalKey = LogicalKeyboardKey.knownLogicalKeys.firstWhere((key) { + return key.debugName?.toLowerCase() == this.key.toLowerCase(); + }); + + final physicalKey = PhysicalKeyboardKey.knownPhysicalKeys.firstWhere((key) { + return logicalKey.debugName == key.debugName; + }); + + await tester.emitKeyEvent( + KeyDownEvent( + physicalKey: physicalKey, + logicalKey: logicalKey, + timeStamp: Duration.zero, + ), + ); + + await tester.pump(duration: Duration(milliseconds: downFor)); + + await tester.emitKeyEvent( + KeyUpEvent( + physicalKey: physicalKey, + logicalKey: logicalKey, + timeStamp: Duration.zero, + ), + ); + + return true; + } + + @override + String description() => 'Press key: $key'; +} diff --git a/packages/fluttium/lib/src/registry.dart b/packages/fluttium/lib/src/registry.dart index 0edeeb73..98cd551f 100644 --- a/packages/fluttium/lib/src/registry.dart +++ b/packages/fluttium/lib/src/registry.dart @@ -1,6 +1,7 @@ import 'dart:collection'; import 'package:fluttium/fluttium.dart'; +import 'package:fluttium/src/actions/press_key.dart'; /// {@template registry} /// The registry of all the actions a [Tester] can perform. @@ -15,6 +16,7 @@ class Registry { 'expectVisible': ActionRegistration(ExpectVisible.new, #text), 'expectNotVisible': ActionRegistration(ExpectNotVisible.new, #text), 'takeScreenshot': ActionRegistration(TakeScreenshot.new, #fileName), + 'pressKey': ActionRegistration(PressKey.new, #key), }; /// Map of all the action that are registered. diff --git a/packages/fluttium/lib/src/tester.dart b/packages/fluttium/lib/src/tester.dart index a6d45e3f..d87809e2 100644 --- a/packages/fluttium/lib/src/tester.dart +++ b/packages/fluttium/lib/src/tester.dart @@ -1,4 +1,9 @@ +import 'dart:async'; +import 'dart:io' if (kIsWeb) ''; +import 'dart:ui'; + import 'package:clock/clock.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart' hide Action; @@ -62,13 +67,712 @@ class Tester { return _binding.handlePointerEvent(event); } + /// Dispatch an event on the keyboard. + Future emitKeyEvent(KeyEvent event) async { + final result = _binding.keyEventManager.handleKeyData( + KeyData( + timeStamp: event.timeStamp, + type: event is KeyDownEvent ? KeyEventType.down : KeyEventType.up, + physical: event.physicalKey.usbHidUsage, + logical: event.logicalKey.keyId, + character: event.character, + synthesized: event.synthesized, + ), + ); + + final data = await emitPlatformMessage( + SystemChannels.keyEvent.name, + SystemChannels.keyEvent.codec.encodeMessage( + getKeyData( + event.logicalKey, + isDown: event is KeyDownEvent, + platform: kIsWeb ? 'web' : Platform.operatingSystem, + physicalKey: event.physicalKey, + character: event.character, + ), + ), + ); + + if (data == null) { + return false; + } + final Map decoded = SystemChannels.keyEvent.codec + .decodeMessage(data)! as Map; + return decoded['handled']! as bool || result; + } + + // Look up a synonym key, and just return the left version of it. + static LogicalKeyboardKey _getKeySynonym(LogicalKeyboardKey origKey) { + if (origKey == LogicalKeyboardKey.shift) { + return LogicalKeyboardKey.shiftLeft; + } + if (origKey == LogicalKeyboardKey.alt) { + return LogicalKeyboardKey.altLeft; + } + if (origKey == LogicalKeyboardKey.meta) { + return LogicalKeyboardKey.metaLeft; + } + if (origKey == LogicalKeyboardKey.control) { + return LogicalKeyboardKey.controlLeft; + } + return origKey; + } + + static PhysicalKeyboardKey _findPhysicalKeyByPlatform( + LogicalKeyboardKey key, + String platform, + ) { + late Map map; + if (kIsWeb) { + // This check is used to treeshake keymap code. + map = kWebToPhysicalKey; + } else { + switch (platform) { + case 'android': + map = kAndroidToPhysicalKey; + break; + case 'fuchsia': + map = kFuchsiaToPhysicalKey; + break; + case 'macos': + map = kMacOsToPhysicalKey; + break; + case 'ios': + map = kIosToPhysicalKey; + break; + case 'linux': + map = kLinuxToPhysicalKey; + break; + case 'web': + map = kWebToPhysicalKey; + break; + case 'windows': + map = kWindowsToPhysicalKey; + break; + } + } + PhysicalKeyboardKey? result; + for (final physicalKey in map.values) { + if (key.debugName == physicalKey.debugName) { + result = physicalKey; + break; + } + } + assert( + result != null, + 'Physical key for $key not found in $platform physical key map', + ); + return result!; + } + + static String? _keyLabel(LogicalKeyboardKey key) { + final keyLabel = key.keyLabel; + if (keyLabel.length == 1) { + return keyLabel.toLowerCase(); + } + return null; + } + + static int _getKeyCode(LogicalKeyboardKey key, String platform) { + if (kIsWeb) { + // web doesn't have int type code. This check is used to treeshake + // keyboard map code. + return -1; + } else { + late Map map; + switch (platform) { + case 'android': + map = kAndroidToLogicalKey; + break; + case 'fuchsia': + map = kFuchsiaToLogicalKey; + break; + case 'macos': + // macOS doesn't do key codes, just scan codes. + return -1; + case 'ios': + // iOS doesn't do key codes, just scan codes. + return -1; + case 'web': + // web doesn't have int type code. + return -1; + case 'linux': + map = kGlfwToLogicalKey; + break; + case 'windows': + map = kWindowsToLogicalKey; + break; + } + int? keyCode; + for (final code in map.keys) { + if (key.keyId == map[code]!.keyId) { + keyCode = code; + break; + } + } + assert(keyCode != null, 'Key $key not found in $platform keyCode map'); + return keyCode!; + } + } + + static int _getScanCode(PhysicalKeyboardKey key, String platform) { + late Map map; + switch (platform) { + case 'android': + map = kAndroidToPhysicalKey; + break; + case 'fuchsia': + map = kFuchsiaToPhysicalKey; + break; + case 'macos': + map = kMacOsToPhysicalKey; + break; + case 'ios': + map = kIosToPhysicalKey; + break; + case 'linux': + map = kLinuxToPhysicalKey; + break; + case 'windows': + map = kWindowsToPhysicalKey; + break; + case 'web': + // web doesn't have int type code + return -1; + } + int? scanCode; + for (final code in map.keys) { + if (key.usbHidUsage == map[code]!.usbHidUsage) { + scanCode = code; + break; + } + } + assert( + scanCode != null, + 'Physical key for $key not found in $platform scanCode map', + ); + return scanCode!; + } + + static _WebKeyLocationPair _getWebKeyLocation( + LogicalKeyboardKey key, + String keyLabel, + ) { + String? result; + for (final entry in kWebLocationMap.entries) { + final foundIndex = entry.value.lastIndexOf(key); + // If foundIndex is -1, then the key is not defined in kWebLocationMap. + // If foundIndex is 0, then the key is in the standard part of the keyboard, + // but we have to check `keyLabel` to see if it's remapped or modified. + if (foundIndex != -1 && foundIndex != 0) { + return _WebKeyLocationPair(entry.key, foundIndex); + } + } + if (keyLabel.isNotEmpty) { + return _WebKeyLocationPair(keyLabel, 0); + } + for (final code in kWebToLogicalKey.keys) { + if (key.keyId == kWebToLogicalKey[code]!.keyId) { + result = code; + break; + } + } + assert(result != null, 'Key $key not found in web keyCode map'); + return _WebKeyLocationPair(result!, 0); + } + + static PhysicalKeyboardKey _inferPhysicalKey(LogicalKeyboardKey key) { + PhysicalKeyboardKey? result; + for (final physicalKey in PhysicalKeyboardKey.knownPhysicalKeys) { + if (physicalKey.debugName == key.debugName) { + result = physicalKey; + break; + } + } + assert(result != null, 'Unable to infer physical key for $key'); + return result!; + } + + static String _getWebCode(PhysicalKeyboardKey key) { + String? result; + for (final entry in kWebToPhysicalKey.entries) { + if (entry.value.usbHidUsage == key.usbHidUsage) { + result = entry.key; + break; + } + } + assert(result != null, 'Key $key not found in web code map'); + return result!; + } + + /// Get a raw key data map given a [LogicalKeyboardKey] and a platform. + static Map getKeyData( + LogicalKeyboardKey key, { + required String platform, + bool isDown = true, + PhysicalKeyboardKey? physicalKey, + String? character, + }) { + key = _getKeySynonym(key); + + // Find a suitable physical key if none was supplied. + physicalKey ??= _findPhysicalKeyByPlatform(key, platform); + + final result = { + 'type': isDown ? 'keydown' : 'keyup', + 'keymap': platform, + }; + + final resultCharacter = character ?? _keyLabel(key) ?? ''; + void assignWeb() { + final keyLocation = _getWebKeyLocation(key, resultCharacter); + final actualPhysicalKey = physicalKey ?? _inferPhysicalKey(key); + result['code'] = _getWebCode(actualPhysicalKey); + result['key'] = keyLocation.key; + result['location'] = keyLocation.location; + result['metaState'] = _getWebModifierFlags(key, isDown); + } + + if (kIsWeb) { + assignWeb(); + return result; + } + final keyCode = _getKeyCode(key, platform); + final scanCode = _getScanCode(physicalKey, platform); + + switch (platform) { + case 'android': + result['keyCode'] = keyCode; + if (resultCharacter.isNotEmpty) { + result['codePoint'] = resultCharacter.codeUnitAt(0); + result['character'] = resultCharacter; + } + result['scanCode'] = scanCode; + result['metaState'] = _getAndroidModifierFlags(key, isDown); + break; + case 'fuchsia': + result['hidUsage'] = physicalKey.usbHidUsage; + if (resultCharacter.isNotEmpty) { + result['codePoint'] = resultCharacter.codeUnitAt(0); + } + result['modifiers'] = _getFuchsiaModifierFlags(key, isDown); + break; + case 'linux': + result['toolkit'] = 'glfw'; + result['keyCode'] = keyCode; + result['scanCode'] = scanCode; + result['modifiers'] = _getGlfwModifierFlags(key, isDown); + result['unicodeScalarValues'] = + resultCharacter.isNotEmpty ? resultCharacter.codeUnitAt(0) : 0; + break; + case 'macos': + result['keyCode'] = scanCode; + if (resultCharacter.isNotEmpty) { + result['characters'] = resultCharacter; + result['charactersIgnoringModifiers'] = resultCharacter; + } + result['modifiers'] = _getMacOsModifierFlags(key, isDown); + break; + case 'ios': + result['keyCode'] = scanCode; + result['characters'] = resultCharacter; + result['charactersIgnoringModifiers'] = resultCharacter; + result['modifiers'] = _getIOSModifierFlags(key, isDown); + break; + case 'windows': + result['keyCode'] = keyCode; + result['scanCode'] = scanCode; + if (resultCharacter.isNotEmpty) { + result['characterCodePoint'] = resultCharacter.codeUnitAt(0); + } + result['modifiers'] = _getWindowsModifierFlags(key, isDown); + break; + case 'web': + assignWeb(); + break; + } + return result; + } + + static int _getAndroidModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataAndroid.modifierLeftShift | + RawKeyEventDataAndroid.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataAndroid.modifierRightShift | + RawKeyEventDataAndroid.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataAndroid.modifierLeftMeta | + RawKeyEventDataAndroid.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataAndroid.modifierRightMeta | + RawKeyEventDataAndroid.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataAndroid.modifierLeftControl | + RawKeyEventDataAndroid.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataAndroid.modifierRightControl | + RawKeyEventDataAndroid.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataAndroid.modifierLeftAlt | + RawKeyEventDataAndroid.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataAndroid.modifierRightAlt | + RawKeyEventDataAndroid.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.fn)) { + result |= RawKeyEventDataAndroid.modifierFunction; + } + if (pressed.contains(LogicalKeyboardKey.scrollLock)) { + result |= RawKeyEventDataAndroid.modifierScrollLock; + } + if (pressed.contains(LogicalKeyboardKey.numLock)) { + result |= RawKeyEventDataAndroid.modifierNumLock; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataAndroid.modifierCapsLock; + } + return result; + } + + static int _getGlfwModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft) || + pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= GLFWKeyHelper.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft) || + pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= GLFWKeyHelper.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft) || + pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= GLFWKeyHelper.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft) || + pressed.contains(LogicalKeyboardKey.altRight)) { + result |= GLFWKeyHelper.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= GLFWKeyHelper.modifierCapsLock; + } + return result; + } + + static int _getWindowsModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shift)) { + result |= RawKeyEventDataWindows.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataWindows.modifierLeftShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataWindows.modifierRightShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataWindows.modifierLeftMeta; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataWindows.modifierRightMeta; + } + if (pressed.contains(LogicalKeyboardKey.control)) { + result |= RawKeyEventDataWindows.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataWindows.modifierLeftControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataWindows.modifierRightControl; + } + if (pressed.contains(LogicalKeyboardKey.alt)) { + result |= RawKeyEventDataWindows.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataWindows.modifierLeftAlt; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataWindows.modifierRightAlt; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataWindows.modifierCaps; + } + if (pressed.contains(LogicalKeyboardKey.numLock)) { + result |= RawKeyEventDataWindows.modifierNumLock; + } + if (pressed.contains(LogicalKeyboardKey.scrollLock)) { + result |= RawKeyEventDataWindows.modifierScrollLock; + } + return result; + } + + static int _getFuchsiaModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataFuchsia.modifierLeftShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataFuchsia.modifierRightShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataFuchsia.modifierLeftMeta; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataFuchsia.modifierRightMeta; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataFuchsia.modifierLeftControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataFuchsia.modifierRightControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataFuchsia.modifierLeftAlt; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataFuchsia.modifierRightAlt; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataFuchsia.modifierCapsLock; + } + return result; + } + + static int _getWebModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataWeb.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataWeb.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataWeb.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataWeb.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataWeb.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataWeb.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataWeb.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataWeb.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataWeb.modifierCapsLock; + } + if (pressed.contains(LogicalKeyboardKey.numLock)) { + result |= RawKeyEventDataWeb.modifierNumLock; + } + if (pressed.contains(LogicalKeyboardKey.scrollLock)) { + result |= RawKeyEventDataWeb.modifierScrollLock; + } + return result; + } + + static int _getMacOsModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataMacOs.modifierLeftShift | + RawKeyEventDataMacOs.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataMacOs.modifierRightShift | + RawKeyEventDataMacOs.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataMacOs.modifierLeftCommand | + RawKeyEventDataMacOs.modifierCommand; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataMacOs.modifierRightCommand | + RawKeyEventDataMacOs.modifierCommand; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataMacOs.modifierLeftControl | + RawKeyEventDataMacOs.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataMacOs.modifierRightControl | + RawKeyEventDataMacOs.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataMacOs.modifierLeftOption | + RawKeyEventDataMacOs.modifierOption; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataMacOs.modifierRightOption | + RawKeyEventDataMacOs.modifierOption; + } + final functionKeys = { + LogicalKeyboardKey.f1, + LogicalKeyboardKey.f2, + LogicalKeyboardKey.f3, + LogicalKeyboardKey.f4, + LogicalKeyboardKey.f5, + LogicalKeyboardKey.f6, + LogicalKeyboardKey.f7, + LogicalKeyboardKey.f8, + LogicalKeyboardKey.f9, + LogicalKeyboardKey.f10, + LogicalKeyboardKey.f11, + LogicalKeyboardKey.f12, + LogicalKeyboardKey.f13, + LogicalKeyboardKey.f14, + LogicalKeyboardKey.f15, + LogicalKeyboardKey.f16, + LogicalKeyboardKey.f17, + LogicalKeyboardKey.f18, + LogicalKeyboardKey.f19, + LogicalKeyboardKey.f20, + LogicalKeyboardKey.f21, + }; + if (pressed.intersection(functionKeys).isNotEmpty) { + result |= RawKeyEventDataMacOs.modifierFunction; + } + if (pressed.intersection(kMacOsNumPadMap.values.toSet()).isNotEmpty) { + result |= RawKeyEventDataMacOs.modifierNumericPad; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataMacOs.modifierCapsLock; + } + return result; + } + + static int _getIOSModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataIos.modifierLeftShift | + RawKeyEventDataIos.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataIos.modifierRightShift | + RawKeyEventDataIos.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataIos.modifierLeftCommand | + RawKeyEventDataIos.modifierCommand; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataIos.modifierRightCommand | + RawKeyEventDataIos.modifierCommand; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataIos.modifierLeftControl | + RawKeyEventDataIos.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataIos.modifierRightControl | + RawKeyEventDataIos.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataIos.modifierLeftOption | + RawKeyEventDataIos.modifierOption; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataIos.modifierRightOption | + RawKeyEventDataIos.modifierOption; + } + final functionKeys = { + LogicalKeyboardKey.f1, + LogicalKeyboardKey.f2, + LogicalKeyboardKey.f3, + LogicalKeyboardKey.f4, + LogicalKeyboardKey.f5, + LogicalKeyboardKey.f6, + LogicalKeyboardKey.f7, + LogicalKeyboardKey.f8, + LogicalKeyboardKey.f9, + LogicalKeyboardKey.f10, + LogicalKeyboardKey.f11, + LogicalKeyboardKey.f12, + LogicalKeyboardKey.f13, + LogicalKeyboardKey.f14, + LogicalKeyboardKey.f15, + LogicalKeyboardKey.f16, + LogicalKeyboardKey.f17, + LogicalKeyboardKey.f18, + LogicalKeyboardKey.f19, + LogicalKeyboardKey.f20, + LogicalKeyboardKey.f21, + }; + if (pressed.intersection(functionKeys).isNotEmpty) { + result |= RawKeyEventDataIos.modifierFunction; + } + if (pressed.intersection(kMacOsNumPadMap.values.toSet()).isNotEmpty) { + result |= RawKeyEventDataIos.modifierNumericPad; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataIos.modifierCapsLock; + } + return result; + } + /// Dispatch a message to the platform. - Future emitPlatformMessage(String channel, ByteData? data) async { + Future emitPlatformMessage(String channel, ByteData? data) async { + final completer = Completer(); await _binding.defaultBinaryMessenger.handlePlatformMessage( channel, data, - (ByteData? data) {}, + completer.complete, ); + return completer.future; } /// Pump the widget tree for the given [duration]. @@ -164,3 +868,13 @@ class Tester { return boundary; } } + +// A tuple of `key` and `location` from Web's `KeyboardEvent` class. +// +// See [RawKeyEventDataWeb]'s `key` and `location` fields for details. +@immutable +class _WebKeyLocationPair { + const _WebKeyLocationPair(this.key, this.location); + final String key; + final int location; +}