From 70007c84ac591bf5971b2e7b0400788fdc63af89 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Thu, 30 Apr 2026 19:11:45 +0900 Subject: [PATCH 1/3] [keyboard_detection_tizen] Introduce keyboard_detection_tizen A Tizen-specific Flutter plugin that detects software keyboard (input panel) visibility by listening to the embedder's 'tizen/internal/inputpanel' event channel. Mirrors the keyboard_detection package API (KeyboardState / KeyboardDetectionController / KeyboardDetection widget). --- .github/recipe.yaml | 1 + README.md | 2 + packages/keyboard_detection_tizen/.gitignore | 26 +++ .../keyboard_detection_tizen/CHANGELOG.md | 3 + packages/keyboard_detection_tizen/LICENSE | 25 ++ packages/keyboard_detection_tizen/README.md | 55 +++++ .../example/.gitignore | 32 +++ .../example/README.md | 7 + .../keyboard_detection_tizen_test.dart | 201 ++++++++++++++++ .../example/lib/main.dart | 110 +++++++++ .../example/pubspec.yaml | 26 +++ .../example/test_driver/integration_test.dart | 3 + .../example/tizen/.gitignore | 12 + .../example/tizen/App.cs | 20 ++ .../example/tizen/Runner.csproj | 19 ++ .../example/tizen/shared/res/ic_launcher.png | Bin 0 -> 1443 bytes .../example/tizen/tizen-manifest.xml | 10 + .../lib/keyboard_detection_tizen.dart | 6 + .../src/keyboard_detection_controller.dart | 217 ++++++++++++++++++ .../keyboard_detection_tizen/pubspec.yaml | 13 ++ 20 files changed, 788 insertions(+) create mode 100644 packages/keyboard_detection_tizen/.gitignore create mode 100644 packages/keyboard_detection_tizen/CHANGELOG.md create mode 100644 packages/keyboard_detection_tizen/LICENSE create mode 100644 packages/keyboard_detection_tizen/README.md create mode 100644 packages/keyboard_detection_tizen/example/.gitignore create mode 100644 packages/keyboard_detection_tizen/example/README.md create mode 100644 packages/keyboard_detection_tizen/example/integration_test/keyboard_detection_tizen_test.dart create mode 100644 packages/keyboard_detection_tizen/example/lib/main.dart create mode 100644 packages/keyboard_detection_tizen/example/pubspec.yaml create mode 100644 packages/keyboard_detection_tizen/example/test_driver/integration_test.dart create mode 100644 packages/keyboard_detection_tizen/example/tizen/.gitignore create mode 100644 packages/keyboard_detection_tizen/example/tizen/App.cs create mode 100644 packages/keyboard_detection_tizen/example/tizen/Runner.csproj create mode 100644 packages/keyboard_detection_tizen/example/tizen/shared/res/ic_launcher.png create mode 100644 packages/keyboard_detection_tizen/example/tizen/tizen-manifest.xml create mode 100644 packages/keyboard_detection_tizen/lib/keyboard_detection_tizen.dart create mode 100644 packages/keyboard_detection_tizen/lib/src/keyboard_detection_controller.dart create mode 100644 packages/keyboard_detection_tizen/pubspec.yaml diff --git a/.github/recipe.yaml b/.github/recipe.yaml index ef8bb5aa7..23fdfb921 100644 --- a/.github/recipe.yaml +++ b/.github/recipe.yaml @@ -6,6 +6,7 @@ plugins: flutter_inappwebview: ["tv-9.0"] flutter_tts: ["tv-9.0"] integration_test: ["tv-9.0"] + keyboard_detection_tizen: ["tv-9.0"] messageport: ["tv-9.0"] package_info_plus: ["tv-9.0"] path_provider: ["tv-9.0"] diff --git a/README.md b/README.md index 1a3b4bb92..d7f91f6d6 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ The _"non-endorsed"_ status means that the plugin is not endorsed by the origina | [**google_sign_in_tizen**](packages/google_sign_in) | [google_sign_in](https://pub.dev/packages/google_sign_in) (1st-party) | [![pub package](https://img.shields.io/pub/v/google_sign_in_tizen.svg)](https://pub.dev/packages/google_sign_in_tizen) | No | | [**in_app_purchase_tizen**](packages/in_app_purchase) | [in_app_purchase](https://pub.dev/packages/in_app_purchase) (1st-party) | [![pub package](https://img.shields.io/pub/v/in_app_purchase_tizen.svg)](https://pub.dev/packages/in_app_purchase_tizen) | No | | [**integration_test_tizen**](packages/integration_test) | [integration_test](https://github.com/flutter/flutter/tree/main/packages/integration_test) (1st-party) | [![pub package](https://img.shields.io/pub/v/integration_test_tizen.svg)](https://pub.dev/packages/integration_test_tizen) | No | +| [**keyboard_detection_tizen**](packages/keyboard_detection_tizen) | (Tizen-only) | [![pub package](https://img.shields.io/pub/v/keyboard_detection_tizen.svg)](https://pub.dev/packages/keyboard_detection_tizen) | N/A | | [**messageport_tizen**](packages/messageport) | (Tizen-only) | [![pub package](https://img.shields.io/pub/v/messageport_tizen.svg)](https://pub.dev/packages/messageport_tizen) | N/A | | [**network_info_plus_tizen**](packages/network_info_plus) | [network_info_plus](https://pub.dev/packages/network_info_plus) (1st-party) | [![pub package](https://img.shields.io/pub/v/network_info_plus_tizen.svg)](https://pub.dev/packages/network_info_plus_tizen) | No | | [**package_info_plus_tizen**](packages/package_info_plus) | [package_info_plus](https://pub.dev/packages/package_info_plus) (1st-party) | [![pub package](https://img.shields.io/pub/v/package_info_plus_tizen.svg)](https://pub.dev/packages/package_info_plus_tizen) | No | @@ -68,6 +69,7 @@ The _"non-endorsed"_ status means that the plugin is not endorsed by the origina | [**google_sign_in_tizen**](packages/google_sign_in) | ✔️ | ✔️ | ✔️ | | [**in_app_purchase_tizen**](packages/in_app_purchase) | ✔️ | ❌ | ❌ | Only applicable for TV | | [**integration_test_tizen**](packages/integration_test) | ✔️ | ✔️ | ✔️ | +| [**keyboard_detection_tizen**](packages/keyboard_detection_tizen) | ✔️ | ✔️ | ✔️ | | [**messageport_tizen**](packages/messageport) | ✔️ | ✔️ | ✔️ | | [**network_info_plus_tizen**](packages/network_info_plus) | ✔️ | ❌ | ✔️ | API not supported on emulator. Need plugin-prebuilt to run wifi on RPI. | | [**package_info_plus_tizen**](packages/package_info_plus) | ✔️ | ✔️ | ✔️ | diff --git a/packages/keyboard_detection_tizen/.gitignore b/packages/keyboard_detection_tizen/.gitignore new file mode 100644 index 000000000..786b65cb9 --- /dev/null +++ b/packages/keyboard_detection_tizen/.gitignore @@ -0,0 +1,26 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ diff --git a/packages/keyboard_detection_tizen/CHANGELOG.md b/packages/keyboard_detection_tizen/CHANGELOG.md new file mode 100644 index 000000000..607323422 --- /dev/null +++ b/packages/keyboard_detection_tizen/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +* Initial release. diff --git a/packages/keyboard_detection_tizen/LICENSE b/packages/keyboard_detection_tizen/LICENSE new file mode 100644 index 000000000..4b455a8ce --- /dev/null +++ b/packages/keyboard_detection_tizen/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2026 Samsung Electronics Co., Ltd. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the copyright holder nor the names of the + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/keyboard_detection_tizen/README.md b/packages/keyboard_detection_tizen/README.md new file mode 100644 index 000000000..f0c72c0f8 --- /dev/null +++ b/packages/keyboard_detection_tizen/README.md @@ -0,0 +1,55 @@ +# keyboard_detection_tizen + +A Tizen-specific Flutter plugin that detects software keyboard (input panel) +visibility and size on Tizen devices. The public API mirrors the +[`keyboard_detection`](https://pub.dev/packages/keyboard_detection) package. + +The original `keyboard_detection` package detects keyboard visibility from +`MediaQuery.viewInsets.bottom`, which is not populated on Tizen. This plugin +listens to the `tizen/internal/inputpanel` event channel exposed by the +flutter-tizen embedder instead, and reads the keyboard geometry (height, +width, position) from the same channel. + +Requires a flutter-tizen embedder that publishes geometry alongside the +state on the `tizen/internal/inputpanel` channel. + +## Usage + +```yaml +dependencies: + keyboard_detection_tizen: ^0.1.0 +``` + +```dart +import 'package:keyboard_detection_tizen/keyboard_detection_tizen.dart'; + +final controller = KeyboardDetectionController( + onChanged: (state) => debugPrint('keyboard: $state'), +); + +@override +Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + const TextField(), + StreamBuilder( + stream: controller.stream, + builder: (_, snapshot) => Text( + 'state: ${snapshot.data ?? KeyboardState.unknown} ' + 'size: ${controller.size}', + ), + ), + ], + ), + ); +} +``` + +## Limitations + +- The Tizen input panel channel emits `show` / `hide` / `will_show` only. + `KeyboardState.hiding` is therefore never reached on Tizen. +- Geometry values are physical pixels reported by `ecore_imf`. Convert with + `MediaQueryData.devicePixelRatio` if you need logical pixels. +- Floating / split keyboards are not supported. diff --git a/packages/keyboard_detection_tizen/example/.gitignore b/packages/keyboard_detection_tizen/example/.gitignore new file mode 100644 index 000000000..7c026a2f1 --- /dev/null +++ b/packages/keyboard_detection_tizen/example/.gitignore @@ -0,0 +1,32 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/packages/keyboard_detection_tizen/example/README.md b/packages/keyboard_detection_tizen/example/README.md new file mode 100644 index 000000000..b12a69776 --- /dev/null +++ b/packages/keyboard_detection_tizen/example/README.md @@ -0,0 +1,7 @@ +# keyboard_detection_tizen_example + +Demonstrates how to use the `keyboard_detection_tizen` plugin. + +## Getting Started + +To run this app on your Tizen device, use [flutter-tizen](https://github.com/flutter-tizen/flutter-tizen). diff --git a/packages/keyboard_detection_tizen/example/integration_test/keyboard_detection_tizen_test.dart b/packages/keyboard_detection_tizen/example/integration_test/keyboard_detection_tizen_test.dart new file mode 100644 index 000000000..d27f5ab47 --- /dev/null +++ b/packages/keyboard_detection_tizen/example/integration_test/keyboard_detection_tizen_test.dart @@ -0,0 +1,201 @@ +// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:keyboard_detection_tizen/keyboard_detection_tizen.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('controller starts in unknown state', ( + WidgetTester tester, + ) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + expect(controller.state, KeyboardState.unknown); + expect(controller.stateAsBool(), isNull); + expect(controller.size, 0); + expect(controller.width, 0); + expect(controller.isSizeLoaded, isFalse); + await controller.dispose(); + }); + + testWidgets('event channel events drive controller state and size', ( + WidgetTester tester, + ) async { + const StandardMethodCodec codec = StandardMethodCodec(); + const String channelName = 'tizen/internal/inputpanel'; + + Future emit(Map payload) async { + final ByteData data = codec.encodeSuccessEnvelope(payload); + await tester.binding.defaultBinaryMessenger.handlePlatformMessage( + channelName, + data, + (_) {}, + ); + } + + final List seen = []; + final KeyboardDetectionController controller = + KeyboardDetectionController(onChanged: seen.add); + + final StreamSubscription sub = controller.stream.listen( + (_) {}, + ); + + await emit({ + 'state': 'will_show', + 'x': 0, + 'y': 0, + 'width': 0, + 'height': 0, + }); + await tester.pump(); + expect(controller.state, KeyboardState.visibling); + expect(controller.size, 0); + + await emit({ + 'state': 'show', + 'x': 0, + 'y': 600, + 'width': 1280, + 'height': 320, + }); + await tester.pump(); + expect(controller.state, KeyboardState.visible); + expect(controller.stateAsBool(), isTrue); + expect(controller.size, 320); + expect(controller.width, 1280); + expect(controller.position.dy, 600); + expect(controller.isSizeLoaded, isTrue); + await expectLater(controller.ensureSizeLoaded, completes); + + await emit({ + 'state': 'hide', + 'x': 0, + 'y': 0, + 'width': 0, + 'height': 0, + }); + await tester.pump(); + expect(controller.state, KeyboardState.hidden); + expect(controller.stateAsBool(), isFalse); + // Geometry keeps the last visible value after hide. + expect(controller.size, 320); + expect(controller.width, 1280); + expect(controller.position.dy, 600); + + expect(seen, [ + KeyboardState.visibling, + KeyboardState.visible, + KeyboardState.hidden, + ]); + + await sub.cancel(); + await controller.dispose(); + }); + + testWidgets('payload without geometry leaves size at zero', ( + WidgetTester tester, + ) async { + const StandardMethodCodec codec = StandardMethodCodec(); + const String channelName = 'tizen/internal/inputpanel'; + + final List seen = []; + final KeyboardDetectionController controller = + KeyboardDetectionController(onChanged: seen.add); + final StreamSubscription sub = controller.stream.listen( + (_) {}, + ); + + final ByteData data = codec.encodeSuccessEnvelope({ + 'state': 'show', + }); + await tester.binding.defaultBinaryMessenger.handlePlatformMessage( + channelName, + data, + (_) {}, + ); + await tester.pump(); + + expect(seen, contains(KeyboardState.visible)); + expect(controller.size, 0); + expect(controller.isSizeLoaded, isFalse); + + await sub.cancel(); + await controller.dispose(); + }); + + testWidgets('real IME show populates size on Tizen', ( + WidgetTester tester, + ) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + final FocusNode focusNode = FocusNode(); + final TextEditingController text = TextEditingController(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: TextField( + controller: text, + focusNode: focusNode, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + final Completer visible = Completer(); + final StreamSubscription sub = + controller.stream.listen((KeyboardState state) { + if (state == KeyboardState.visible && !visible.isCompleted) { + visible.complete(); + } + }); + + focusNode.requestFocus(); + await tester.pumpAndSettle(); + // Force the platform IME to show since `tester.tap` does not trigger it + // on a headless integration test. + await SystemChannels.textInput.invokeMethod('TextInput.show'); + + await visible.future.timeout( + const Duration(seconds: 6), + onTimeout: () { + // Some emulator profiles may not actually open an IME. Don't fail + // here since the fixed-payload tests above already cover parsing. + }, + ); + await tester.pump(const Duration(milliseconds: 500)); + + if (controller.state == KeyboardState.visible) { + expect( + controller.size, + greaterThan(0), + reason: 'embedder should report a non-zero IME height when visible', + ); + debugPrint( + 'IME geometry observed: size=${controller.size}, ' + 'width=${controller.width}, position=${controller.position}', + ); + } else { + debugPrint( + 'IME did not surface on this emulator profile; ' + 'controller.state=${controller.state}', + ); + } + + await SystemChannels.textInput.invokeMethod('TextInput.hide'); + await sub.cancel(); + await controller.dispose(); + }); +} diff --git a/packages/keyboard_detection_tizen/example/lib/main.dart b/packages/keyboard_detection_tizen/example/lib/main.dart new file mode 100644 index 000000000..774a5c0a6 --- /dev/null +++ b/packages/keyboard_detection_tizen/example/lib/main.dart @@ -0,0 +1,110 @@ +// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'package:flutter/material.dart'; +import 'package:keyboard_detection_tizen/keyboard_detection_tizen.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'keyboard_detection_tizen example', + theme: ThemeData(primarySwatch: Colors.blue), + home: const HomePage(), + ); + } +} + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + late final KeyboardDetectionController _controller; + + @override + void initState() { + super.initState(); + _controller = KeyboardDetectionController( + onChanged: (KeyboardState state) { + debugPrint('keyboard_detection_tizen: $state'); + }, + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Keyboard Detection')), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + StreamBuilder( + stream: _controller.stream, + initialData: _controller.state, + builder: (BuildContext _, AsyncSnapshot snap) { + final KeyboardState s = snap.data ?? KeyboardState.unknown; + return Column( + key: const Key('status'), + children: [ + Text( + 'state: ${s.name}', + key: const Key('state-text'), + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + Text( + 'visible: ${_controller.stateAsBool() ?? "unknown"}', + key: const Key('visible-text'), + ), + const SizedBox(height: 8), + Text( + 'width: ${_controller.width.toStringAsFixed(1)} ' + '/ size(height): ${_controller.size.toStringAsFixed(1)} ', + key: const Key('size-text'), + ), + const SizedBox(height: 8), + Text( + 'position: ' + '(${_controller.position.dx.toStringAsFixed(1)}, ' + '${_controller.position.dy.toStringAsFixed(1)})', + key: const Key('position-text'), + ), + ], + ); + }, + ), + const SizedBox(height: 32), + const TextField( + decoration: InputDecoration( + labelText: 'Tap here to open keyboard', + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/packages/keyboard_detection_tizen/example/pubspec.yaml b/packages/keyboard_detection_tizen/example/pubspec.yaml new file mode 100644 index 000000000..d20c3ea35 --- /dev/null +++ b/packages/keyboard_detection_tizen/example/pubspec.yaml @@ -0,0 +1,26 @@ +name: keyboard_detection_tizen_example +description: Demonstrates how to use the keyboard_detection_tizen plugin. +publish_to: "none" + +environment: + sdk: ">=3.1.0 <4.0.0" + flutter: ">=3.13.0" + +dependencies: + flutter: + sdk: flutter + keyboard_detection_tizen: + path: ../ + +dev_dependencies: + flutter_driver: + sdk: flutter + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + integration_test_tizen: + path: ../../integration_test/ + +flutter: + uses-material-design: true diff --git a/packages/keyboard_detection_tizen/example/test_driver/integration_test.dart b/packages/keyboard_detection_tizen/example/test_driver/integration_test.dart new file mode 100644 index 000000000..b38629cca --- /dev/null +++ b/packages/keyboard_detection_tizen/example/test_driver/integration_test.dart @@ -0,0 +1,3 @@ +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/packages/keyboard_detection_tizen/example/tizen/.gitignore b/packages/keyboard_detection_tizen/example/tizen/.gitignore new file mode 100644 index 000000000..ac0738be2 --- /dev/null +++ b/packages/keyboard_detection_tizen/example/tizen/.gitignore @@ -0,0 +1,12 @@ +flutter/ +.vs/ +*.user +bin/ +obj/ + +# Tizen Core CLI (tz) related files +tizen_dotnet_project.yaml +*.csproj.backup + +# Flutter-tizen dependency information file +.app.deps.json diff --git a/packages/keyboard_detection_tizen/example/tizen/App.cs b/packages/keyboard_detection_tizen/example/tizen/App.cs new file mode 100644 index 000000000..b4f1b3ee5 --- /dev/null +++ b/packages/keyboard_detection_tizen/example/tizen/App.cs @@ -0,0 +1,20 @@ +using Tizen.Flutter.Embedding; + +namespace Runner +{ + public class App : FlutterApplication + { + protected override void OnCreate() + { + base.OnCreate(); + + GeneratedPluginRegistrant.RegisterPlugins(this); + } + + static void Main(string[] args) + { + var app = new App(); + app.Run(args); + } + } +} diff --git a/packages/keyboard_detection_tizen/example/tizen/Runner.csproj b/packages/keyboard_detection_tizen/example/tizen/Runner.csproj new file mode 100644 index 000000000..8c5646474 --- /dev/null +++ b/packages/keyboard_detection_tizen/example/tizen/Runner.csproj @@ -0,0 +1,19 @@ + + + + Exe + tizen80 + + + + + + + + + + %(RecursiveDir) + + + + diff --git a/packages/keyboard_detection_tizen/example/tizen/shared/res/ic_launcher.png b/packages/keyboard_detection_tizen/example/tizen/shared/res/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/packages/keyboard_detection_tizen/example/tizen/tizen-manifest.xml b/packages/keyboard_detection_tizen/example/tizen/tizen-manifest.xml new file mode 100644 index 000000000..e669278ea --- /dev/null +++ b/packages/keyboard_detection_tizen/example/tizen/tizen-manifest.xml @@ -0,0 +1,10 @@ + + + + + + ic_launcher.png + + + + diff --git a/packages/keyboard_detection_tizen/lib/keyboard_detection_tizen.dart b/packages/keyboard_detection_tizen/lib/keyboard_detection_tizen.dart new file mode 100644 index 000000000..315db665e --- /dev/null +++ b/packages/keyboard_detection_tizen/lib/keyboard_detection_tizen.dart @@ -0,0 +1,6 @@ +// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'src/keyboard_detection_controller.dart' + show KeyboardDetectionCallback, KeyboardDetectionController, KeyboardState; diff --git a/packages/keyboard_detection_tizen/lib/src/keyboard_detection_controller.dart b/packages/keyboard_detection_tizen/lib/src/keyboard_detection_controller.dart new file mode 100644 index 000000000..81f5f2449 --- /dev/null +++ b/packages/keyboard_detection_tizen/lib/src/keyboard_detection_controller.dart @@ -0,0 +1,217 @@ +// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/services.dart'; + +/// Possible visibility states for the software keyboard (input panel). +enum KeyboardState { + /// The state has not yet been reported by the platform. + unknown, + + /// The keyboard is fully visible. + visible, + + /// The keyboard is animating from hidden to visible. + visibling, + + /// The keyboard is fully hidden. + hidden, + + /// The keyboard is animating from visible to hidden. + /// + /// On Tizen the platform does not emit a "will hide" event, so this state is + /// not reached on Tizen devices. It is kept for API parity with the + /// `keyboard_detection` package. + hiding, +} + +/// A callback invoked when the keyboard state changes. +/// +/// Return `true` to keep receiving events, `false` to unregister automatically. +typedef KeyboardDetectionCallback = + FutureOr Function(KeyboardState state); + +/// Detects software keyboard visibility on Tizen by listening to the +/// `tizen/internal/inputpanel` event channel exposed by the flutter-tizen +/// embedder. +/// +/// Mirrors the API of the [keyboard_detection](https://pub.dev/packages/keyboard_detection) +/// package so application code can be ported to Tizen with minimal changes. +class KeyboardDetectionController { + /// Creates a controller and immediately starts listening to keyboard events. + /// + /// [onChanged] is called on every state change. Use [registerCallback] to + /// add additional listeners that may auto-unregister. + KeyboardDetectionController({this.onChanged}) { + _subscription = _channel.receiveBroadcastStream().listen( + _handleEvent, + onError: (Object _) { + // Stay in unknown state if the channel raises an error (e.g. when + // running on a host that does not provide the input panel channel). + }, + ); + } + + static const EventChannel _channel = EventChannel( + 'tizen/internal/inputpanel', + ); + + /// Called whenever the keyboard state changes. + final void Function(KeyboardState state)? onChanged; + + late final StreamSubscription _subscription; + final Map _callbacks = + {}; + final StreamController _streamController = + StreamController.broadcast(); + final Completer _sizeLoaded = Completer(); + + KeyboardState _state = KeyboardState.unknown; + bool? _stateAsBool; + double _x = 0; + double _y = 0; + double _width = 0; + double _size = 0; + + /// Broadcast stream of keyboard state changes. + Stream get stream => _streamController.stream; + + /// The latest reported keyboard state. + KeyboardState get state => _state; + + /// The last observed keyboard height in physical pixels reported by the + /// Tizen input method context. + /// + /// Returns `0` until the keyboard has been visible at least once. + double get size => _size; + + /// The last observed keyboard width in physical pixels. + double get width => _width; + + /// The last observed top-left position of the keyboard in physical pixels. + Offset get position => Offset(_x, _y); + + /// Whether [size] has been observed at least once. + bool get isSizeLoaded => _sizeLoaded.isCompleted; + + /// Resolves once [size] has a non-zero value. + Future get ensureSizeLoaded => _sizeLoaded.future; + + /// Returns the current visibility as a boolean. + /// + /// - `null` while the state is [KeyboardState.unknown]. + /// - When [includeTransitionalState] is `false` (default), only + /// [KeyboardState.visible] / [KeyboardState.hidden] update the result. + /// - When `true`, the transitional [KeyboardState.visibling] / + /// [KeyboardState.hiding] states also update the result. + bool? stateAsBool([bool includeTransitionalState = false]) { + if (_state == KeyboardState.unknown) { + _stateAsBool = null; + } + if (includeTransitionalState) { + if (_state == KeyboardState.visibling) { + _stateAsBool = true; + } + if (_state == KeyboardState.hiding) { + _stateAsBool = false; + } + } + if (_state == KeyboardState.visible) { + _stateAsBool = true; + } + if (_state == KeyboardState.hidden) { + _stateAsBool = false; + } + return _stateAsBool; + } + + /// Registers a callback to be invoked on every keyboard state change. + /// + /// The callback is removed automatically the first time it returns `false`. + void registerCallback(KeyboardDetectionCallback callback) { + _callbacks[callback] = true; + } + + /// Removes a previously registered callback. + void unregisterCallback(KeyboardDetectionCallback callback) { + _callbacks.remove(callback); + } + + /// Removes all registered callbacks. + void unregisterAllCallbacks() { + _callbacks.clear(); + } + + /// Releases native subscriptions. Call when the controller is no longer + /// needed, typically from a [State.dispose]. + Future dispose() async { + await _subscription.cancel(); + await _streamController.close(); + } + + void _handleEvent(dynamic event) { + if (event is! Map) { + return; + } + final Object? state = event['state']; + if (state is! String) { + return; + } + _width = _readNumber(event['width']) ?? 0; + _size = _readNumber(event['height']) ?? 0; + _x = _readNumber(event['x']) ?? 0; + _y = _readNumber(event['y']) ?? 0; + if (!_sizeLoaded.isCompleted) { + _sizeLoaded.complete(true); + } + + KeyboardState next; + switch (state) { + case 'will_show': + next = KeyboardState.visibling; + case 'show': + next = KeyboardState.visible; + case 'will_hide': + next = KeyboardState.hiding; + case 'hide': + next = KeyboardState.hidden; + default: + next = KeyboardState.unknown; + } + _setState(next); + } + + static double? _readNumber(Object? value) { + if (value is num) { + return value.toDouble(); + } + return null; + } + + void _setState(KeyboardState next) { + _state = next; + if (!_streamController.isClosed) { + _streamController.add(next); + } + onChanged?.call(next); + unawaited(_executeCallbacks(next)); + } + + Future _executeCallbacks(KeyboardState state) async { + final List> entries = _callbacks + .entries + .where((MapEntry e) { + return e.value; + }) + .toList(); + await Future.wait( + entries.map((MapEntry e) async { + _callbacks[e.key] = await e.key(state); + }), + ); + _callbacks.removeWhere((KeyboardDetectionCallback _, bool keep) => !keep); + } +} diff --git a/packages/keyboard_detection_tizen/pubspec.yaml b/packages/keyboard_detection_tizen/pubspec.yaml new file mode 100644 index 000000000..0aeab3a72 --- /dev/null +++ b/packages/keyboard_detection_tizen/pubspec.yaml @@ -0,0 +1,13 @@ +name: keyboard_detection_tizen +description: A Tizen-specific Flutter plugin that detects software keyboard (input panel) visibility and state changes. +homepage: https://github.com/flutter-tizen/plugins +repository: https://github.com/flutter-tizen/plugins/tree/master/packages/keyboard_detection_tizen +version: 0.1.0 + +environment: + sdk: ">=3.1.0 <4.0.0" + flutter: ">=3.13.0" + +dependencies: + flutter: + sdk: flutter From e5bd46f020e5fc78238b8063022fa23ca5e26b44 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Thu, 14 May 2026 15:12:32 +0900 Subject: [PATCH 2/3] Apply review feedback --- .../keyboard_detection_tizen_test.dart | 8 +- .../src/keyboard_detection_controller.dart | 95 ++++++++----------- 2 files changed, 45 insertions(+), 58 deletions(-) diff --git a/packages/keyboard_detection_tizen/example/integration_test/keyboard_detection_tizen_test.dart b/packages/keyboard_detection_tizen/example/integration_test/keyboard_detection_tizen_test.dart index d27f5ab47..90ca0e261 100644 --- a/packages/keyboard_detection_tizen/example/integration_test/keyboard_detection_tizen_test.dart +++ b/packages/keyboard_detection_tizen/example/integration_test/keyboard_detection_tizen_test.dart @@ -86,10 +86,10 @@ void main() { await tester.pump(); expect(controller.state, KeyboardState.hidden); expect(controller.stateAsBool(), isFalse); - // Geometry keeps the last visible value after hide. - expect(controller.size, 320); - expect(controller.width, 1280); - expect(controller.position.dy, 600); + // Geometry resets to zero once the keyboard is hidden. + expect(controller.size, 0); + expect(controller.width, 0); + expect(controller.position, Offset.zero); expect(seen, [ KeyboardState.visibling, diff --git a/packages/keyboard_detection_tizen/lib/src/keyboard_detection_controller.dart b/packages/keyboard_detection_tizen/lib/src/keyboard_detection_controller.dart index 81f5f2449..8ff910dfe 100644 --- a/packages/keyboard_detection_tizen/lib/src/keyboard_detection_controller.dart +++ b/packages/keyboard_detection_tizen/lib/src/keyboard_detection_controller.dart @@ -70,7 +70,6 @@ class KeyboardDetectionController { final Completer _sizeLoaded = Completer(); KeyboardState _state = KeyboardState.unknown; - bool? _stateAsBool; double _x = 0; double _y = 0; double _width = 0; @@ -103,29 +102,17 @@ class KeyboardDetectionController { /// Returns the current visibility as a boolean. /// /// - `null` while the state is [KeyboardState.unknown]. - /// - When [includeTransitionalState] is `false` (default), only - /// [KeyboardState.visible] / [KeyboardState.hidden] update the result. - /// - When `true`, the transitional [KeyboardState.visibling] / - /// [KeyboardState.hiding] states also update the result. + /// - When [includeTransitionalState] is `false` (default), the transitional + /// [KeyboardState.visibling] / [KeyboardState.hiding] states are mapped to + /// their stable counterpart so the value does not depend on prior calls. bool? stateAsBool([bool includeTransitionalState = false]) { - if (_state == KeyboardState.unknown) { - _stateAsBool = null; - } - if (includeTransitionalState) { - if (_state == KeyboardState.visibling) { - _stateAsBool = true; - } - if (_state == KeyboardState.hiding) { - _stateAsBool = false; - } - } - if (_state == KeyboardState.visible) { - _stateAsBool = true; - } - if (_state == KeyboardState.hidden) { - _stateAsBool = false; - } - return _stateAsBool; + return switch (_state) { + KeyboardState.unknown => null, + KeyboardState.visibling => includeTransitionalState, + KeyboardState.visible => true, + KeyboardState.hiding => !includeTransitionalState, + KeyboardState.hidden => false, + }; } /// Registers a callback to be invoked on every keyboard state change. @@ -160,27 +147,26 @@ class KeyboardDetectionController { if (state is! String) { return; } - _width = _readNumber(event['width']) ?? 0; - _size = _readNumber(event['height']) ?? 0; - _x = _readNumber(event['x']) ?? 0; - _y = _readNumber(event['y']) ?? 0; - if (!_sizeLoaded.isCompleted) { - _sizeLoaded.complete(true); - } - KeyboardState next; - switch (state) { - case 'will_show': - next = KeyboardState.visibling; - case 'show': - next = KeyboardState.visible; - case 'will_hide': - next = KeyboardState.hiding; - case 'hide': - next = KeyboardState.hidden; - default: - next = KeyboardState.unknown; + final double? width = _readNumber(event['width']); + final double? height = _readNumber(event['height']); + if (width != null && height != null && width > 0 && height > 0) { + _width = width; + _size = height; + _x = _readNumber(event['x']) ?? 0; + _y = _readNumber(event['y']) ?? 0; + if (!_sizeLoaded.isCompleted) { + _sizeLoaded.complete(true); + } } + + final KeyboardState next = switch (state) { + 'will_show' => KeyboardState.visibling, + 'show' => KeyboardState.visible, + 'will_hide' => KeyboardState.hiding, + 'hide' => KeyboardState.hidden, + _ => KeyboardState.unknown, + }; _setState(next); } @@ -201,17 +187,18 @@ class KeyboardDetectionController { } Future _executeCallbacks(KeyboardState state) async { - final List> entries = _callbacks - .entries - .where((MapEntry e) { - return e.value; - }) - .toList(); - await Future.wait( - entries.map((MapEntry e) async { - _callbacks[e.key] = await e.key(state); - }), - ); - _callbacks.removeWhere((KeyboardDetectionCallback _, bool keep) => !keep); + final List targets = _callbacks.keys.toList(); + for (final KeyboardDetectionCallback callback in targets) { + if (!_callbacks.containsKey(callback)) { + continue; + } + final bool keep = await callback(state); + if (!_callbacks.containsKey(callback)) { + continue; + } + if (!keep) { + _callbacks.remove(callback); + } + } } } From 9ff00fbc1e44b989f90df4c16d3c662d9143126a Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Thu, 14 May 2026 16:01:05 +0900 Subject: [PATCH 3/3] Reset geometry on hide, focus tests on visibility --- .../keyboard_detection_tizen_test.dart | 193 ++---------------- .../src/keyboard_detection_controller.dart | 9 +- 2 files changed, 26 insertions(+), 176 deletions(-) diff --git a/packages/keyboard_detection_tizen/example/integration_test/keyboard_detection_tizen_test.dart b/packages/keyboard_detection_tizen/example/integration_test/keyboard_detection_tizen_test.dart index 90ca0e261..4a8ba1164 100644 --- a/packages/keyboard_detection_tizen/example/integration_test/keyboard_detection_tizen_test.dart +++ b/packages/keyboard_detection_tizen/example/integration_test/keyboard_detection_tizen_test.dart @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -13,189 +10,37 @@ import 'package:keyboard_detection_tizen/keyboard_detection_tizen.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('controller starts in unknown state', ( - WidgetTester tester, - ) async { - final KeyboardDetectionController controller = - KeyboardDetectionController(); - expect(controller.state, KeyboardState.unknown); - expect(controller.stateAsBool(), isNull); - expect(controller.size, 0); - expect(controller.width, 0); - expect(controller.isSizeLoaded, isFalse); - await controller.dispose(); - }); - - testWidgets('event channel events drive controller state and size', ( - WidgetTester tester, - ) async { - const StandardMethodCodec codec = StandardMethodCodec(); - const String channelName = 'tizen/internal/inputpanel'; - - Future emit(Map payload) async { - final ByteData data = codec.encodeSuccessEnvelope(payload); - await tester.binding.defaultBinaryMessenger.handlePlatformMessage( - channelName, - data, - (_) {}, - ); - } - - final List seen = []; - final KeyboardDetectionController controller = - KeyboardDetectionController(onChanged: seen.add); - - final StreamSubscription sub = controller.stream.listen( - (_) {}, - ); - - await emit({ - 'state': 'will_show', - 'x': 0, - 'y': 0, - 'width': 0, - 'height': 0, - }); - await tester.pump(); - expect(controller.state, KeyboardState.visibling); - expect(controller.size, 0); - - await emit({ - 'state': 'show', - 'x': 0, - 'y': 600, - 'width': 1280, - 'height': 320, - }); - await tester.pump(); - expect(controller.state, KeyboardState.visible); - expect(controller.stateAsBool(), isTrue); - expect(controller.size, 320); - expect(controller.width, 1280); - expect(controller.position.dy, 600); - expect(controller.isSizeLoaded, isTrue); - await expectLater(controller.ensureSizeLoaded, completes); - - await emit({ - 'state': 'hide', - 'x': 0, - 'y': 0, - 'width': 0, - 'height': 0, - }); - await tester.pump(); - expect(controller.state, KeyboardState.hidden); - expect(controller.stateAsBool(), isFalse); - // Geometry resets to zero once the keyboard is hidden. - expect(controller.size, 0); - expect(controller.width, 0); - expect(controller.position, Offset.zero); - - expect(seen, [ - KeyboardState.visibling, - KeyboardState.visible, - KeyboardState.hidden, - ]); - - await sub.cancel(); - await controller.dispose(); - }); - - testWidgets('payload without geometry leaves size at zero', ( - WidgetTester tester, - ) async { - const StandardMethodCodec codec = StandardMethodCodec(); - const String channelName = 'tizen/internal/inputpanel'; + const String channelName = 'tizen/internal/inputpanel'; + const StandardMethodCodec codec = StandardMethodCodec(); - final List seen = []; - final KeyboardDetectionController controller = - KeyboardDetectionController(onChanged: seen.add); - final StreamSubscription sub = controller.stream.listen( - (_) {}, - ); - - final ByteData data = codec.encodeSuccessEnvelope({ - 'state': 'show', - }); + Future emit(WidgetTester tester, Map payload) async { + final ByteData data = codec.encodeSuccessEnvelope(payload); await tester.binding.defaultBinaryMessenger.handlePlatformMessage( channelName, data, (_) {}, ); - await tester.pump(); + } - expect(seen, contains(KeyboardState.visible)); - expect(controller.size, 0); - expect(controller.isSizeLoaded, isFalse); - - await sub.cancel(); + testWidgets('reports visible on show event', (WidgetTester tester) async { + final KeyboardDetectionController controller = + KeyboardDetectionController(); + await emit(tester, {'state': 'show'}); + await tester.pump(); + expect(controller.state, KeyboardState.visible); + expect(controller.stateAsBool(), isTrue); await controller.dispose(); }); - testWidgets('real IME show populates size on Tizen', ( - WidgetTester tester, - ) async { + testWidgets('reports hidden on hide event', (WidgetTester tester) async { final KeyboardDetectionController controller = KeyboardDetectionController(); - final FocusNode focusNode = FocusNode(); - final TextEditingController text = TextEditingController(); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: TextField( - controller: text, - focusNode: focusNode, - ), - ), - ), - ), - ); - await tester.pumpAndSettle(); - - final Completer visible = Completer(); - final StreamSubscription sub = - controller.stream.listen((KeyboardState state) { - if (state == KeyboardState.visible && !visible.isCompleted) { - visible.complete(); - } - }); - - focusNode.requestFocus(); - await tester.pumpAndSettle(); - // Force the platform IME to show since `tester.tap` does not trigger it - // on a headless integration test. - await SystemChannels.textInput.invokeMethod('TextInput.show'); - - await visible.future.timeout( - const Duration(seconds: 6), - onTimeout: () { - // Some emulator profiles may not actually open an IME. Don't fail - // here since the fixed-payload tests above already cover parsing. - }, - ); - await tester.pump(const Duration(milliseconds: 500)); - - if (controller.state == KeyboardState.visible) { - expect( - controller.size, - greaterThan(0), - reason: 'embedder should report a non-zero IME height when visible', - ); - debugPrint( - 'IME geometry observed: size=${controller.size}, ' - 'width=${controller.width}, position=${controller.position}', - ); - } else { - debugPrint( - 'IME did not surface on this emulator profile; ' - 'controller.state=${controller.state}', - ); - } - - await SystemChannels.textInput.invokeMethod('TextInput.hide'); - await sub.cancel(); + await emit(tester, {'state': 'show'}); + await tester.pump(); + await emit(tester, {'state': 'hide'}); + await tester.pump(); + expect(controller.state, KeyboardState.hidden); + expect(controller.stateAsBool(), isFalse); await controller.dispose(); }); } diff --git a/packages/keyboard_detection_tizen/lib/src/keyboard_detection_controller.dart b/packages/keyboard_detection_tizen/lib/src/keyboard_detection_controller.dart index 8ff910dfe..c7274a76b 100644 --- a/packages/keyboard_detection_tizen/lib/src/keyboard_detection_controller.dart +++ b/packages/keyboard_detection_tizen/lib/src/keyboard_detection_controller.dart @@ -31,8 +31,8 @@ enum KeyboardState { /// A callback invoked when the keyboard state changes. /// /// Return `true` to keep receiving events, `false` to unregister automatically. -typedef KeyboardDetectionCallback = - FutureOr Function(KeyboardState state); +typedef KeyboardDetectionCallback = FutureOr Function( + KeyboardState state); /// Detects software keyboard visibility on Tizen by listening to the /// `tizen/internal/inputpanel` event channel exposed by the flutter-tizen @@ -158,6 +158,11 @@ class KeyboardDetectionController { if (!_sizeLoaded.isCompleted) { _sizeLoaded.complete(true); } + } else if (state == 'hide') { + _width = 0; + _size = 0; + _x = 0; + _y = 0; } final KeyboardState next = switch (state) {