Skip to content

Commit 01a7ac8

Browse files
committed
[green] dismiss native keyboards before expanding the console
Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent 62b077c commit 01a7ac8

6 files changed

Lines changed: 94 additions & 15 deletions

File tree

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
1+
// SPDX-License-Identifier: MIT
2+
// Part of PyBLE (https://pyble.dev) — see /LICENSE.
3+
14
package dev.pyble.pyble
25

6+
import android.view.inputmethod.InputMethodManager
37
import io.flutter.embedding.android.FlutterActivity
8+
import io.flutter.embedding.engine.FlutterEngine
9+
import io.flutter.plugin.common.MethodChannel
410

5-
class MainActivity : FlutterActivity()
11+
class MainActivity : FlutterActivity() {
12+
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
13+
super.configureFlutterEngine(flutterEngine)
14+
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "dev.pyble/keyboard")
15+
.setMethodCallHandler { call, result ->
16+
if (call.method == "dismissKeyboard") {
17+
val manager = getSystemService(InputMethodManager::class.java)
18+
window.decorView.windowToken?.let { token ->
19+
manager?.hideSoftInputFromWindow(token, 0)
20+
}
21+
// Acknowledge the request; Flutter observes actual view insets.
22+
result.success(null)
23+
} else {
24+
result.notImplemented()
25+
}
26+
}
27+
}
28+
}

‎app/ios/Runner/AppDelegate.swift‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// SPDX-License-Identifier: MIT
2+
// Part of PyBLE (https://pyble.dev) — see /LICENSE.
3+
14
import Flutter
25
import UIKit
36

@@ -12,5 +15,19 @@ import UIKit
1215

1316
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
1417
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
18+
guard let registrar = engineBridge.pluginRegistry.registrar(forPlugin: "PybleKeyboardDismissal")
19+
else { return }
20+
let keyboard = FlutterMethodChannel(
21+
name: "dev.pyble/keyboard", binaryMessenger: registrar.messenger())
22+
keyboard.setMethodCallHandler { [weak registrar] call, result in
23+
guard call.method == "dismissKeyboard" else {
24+
result(FlutterMethodNotImplemented)
25+
return
26+
}
27+
// End editing in this engine's window, including embedded WKWebView fields.
28+
registrar?.viewController?.viewIfLoaded?.window?.endEditing(true)
29+
// Acknowledge the request; Flutter observes actual view insets.
30+
result(nil)
31+
}
1532
}
1633
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: MIT
2+
// Part of PyBLE (https://pyble.dev) — see /LICENSE.
3+
4+
import 'package:flutter/foundation.dart';
5+
import 'package:flutter/services.dart';
6+
7+
const MethodChannel _keyboardChannel = MethodChannel('dev.pyble/keyboard');
8+
9+
/// Requests dismissal of the current window's keyboard, including native
10+
/// WebView fields. Completion does not assert that the keyboard is hidden;
11+
/// layout continues to follow the actual view insets.
12+
Future<void> dismissSoftwareKeyboard() async {
13+
if (!kIsWeb &&
14+
(defaultTargetPlatform == TargetPlatform.android ||
15+
defaultTargetPlatform == TargetPlatform.iOS)) {
16+
await _keyboardChannel.invokeMethod<void>('dismissKeyboard');
17+
} else {
18+
await SystemChannels.textInput.invokeMethod<void>('TextInput.hide');
19+
}
20+
}

‎app/lib/app/shell.dart‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import 'package:pyble/localization/localization.dart';
2525
import 'package:pyble/pble/pble.dart';
2626
import 'package:pyble/theme/theme.dart';
2727

28+
import 'keyboard_dismissal.dart';
2829
import 'pages/about_page.dart';
2930
import 'pages/blocks_page.dart';
3031
import 'pages/connect_page.dart';
@@ -335,12 +336,13 @@ class _LandscapeShell extends ConsumerWidget {
335336
collapsed:
336337
keyboardVisible || (blocksFocused && !blocksConsoleExpanded),
337338
onToggle: keyboardVisible
338-
? () {
339+
? () async {
339340
if (blocksFocused) {
340341
ref.read(blocksConsoleExpandedProvider.notifier).state =
341342
true;
342343
}
343344
FocusScope.of(context).unfocus();
345+
await dismissSoftwareKeyboard();
344346
}
345347
: blocksFocused
346348
? () {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-License-Identifier: MIT
2+
// Part of PyBLE (https://pyble.dev) — see /LICENSE.
3+
4+
import 'package:flutter/foundation.dart';
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:pyble/app/keyboard_dismissal.dart';
7+
8+
void main() {
9+
testWidgets(
10+
'a platform without the app native handler uses the Flutter hide request',
11+
(WidgetTester tester) async {
12+
tester.testTextInput.log.clear();
13+
await dismissSoftwareKeyboard();
14+
expect(
15+
tester.testTextInput.log.map((call) => call.method),
16+
contains('TextInput.hide'),
17+
);
18+
},
19+
variant: TargetPlatformVariant(<TargetPlatform>{TargetPlatform.linux}),
20+
);
21+
}

‎app/test/widget/app_shell_test.dart‎

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
// toolbar action slots) without weakening any of it.
2222
// HAND-OFF provenance: lib/app → app-architect; lib/pble → app-protocol-engineer.
2323

24-
import 'dart:typed_data';
25-
2624
import 'package:flutter/foundation.dart';
2725
import 'package:flutter/material.dart';
2826
import 'package:flutter/rendering.dart';
@@ -95,15 +93,9 @@ Future<void> _focusLandscapeBlocks(WidgetTester tester) async {
9593

9694
void main() {
9795
group('landscape software keyboard', () {
98-
for (final TargetPlatform platform in <TargetPlatform>[
99-
TargetPlatform.android,
100-
TargetPlatform.iOS,
101-
]) {
102-
testWidgets('Expand dismisses a native Blocks keyboard on $platform', (
103-
WidgetTester tester,
104-
) async {
105-
debugDefaultTargetPlatformOverride = platform;
106-
addTearDown(() => debugDefaultTargetPlatformOverride = null);
96+
testWidgets(
97+
'Expand requests native Blocks keyboard dismissal',
98+
(WidgetTester tester) async {
10799
const MethodChannel keyboard = MethodChannel('dev.pyble/keyboard');
108100
final List<MethodCall> requests = <MethodCall>[];
109101
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
@@ -154,8 +146,12 @@ void main() {
154146
);
155147
expect(tester.element(find.byKey(_blocksWorkspaceHostKey)), same(host));
156148
expect(tester.takeException(), isNull);
157-
});
158-
}
149+
},
150+
variant: TargetPlatformVariant(<TargetPlatform>{
151+
TargetPlatform.android,
152+
TargetPlatform.iOS,
153+
}),
154+
);
159155

160156
testWidgets('Expand preserves a Flutter editor draft and selection', (
161157
WidgetTester tester,

0 commit comments

Comments
 (0)