From f949b008b301dc79d7d29a44af1015820d2f7306 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Mon, 18 Sep 2023 20:50:08 +0200 Subject: [PATCH] feat(android): Add the keyboard height code --- README.md | 4 + .../org/moxxy/moxxy_native/Constants.kt | 3 + .../moxxy/moxxy_native/MoxxyNativePlugin.kt | 9 +++ .../notifications/NotificationReceiver.kt | 2 +- .../platform/KeyboardStreamHandler.kt | 73 +++++++++++++++++++ example/lib/main.dart | 15 +++- 6 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 android/src/main/kotlin/org/moxxy/moxxy_native/platform/KeyboardStreamHandler.kt diff --git a/README.md b/README.md index acc9947..356446a 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,7 @@ Thanks to [ekasetiawans](https://github.com/ekasetiawans) for [flutter_backgroun was essentially the blueprint for the service and background service APIs. They were reimplemented to allow the root isolate to pass some additional data to the service, which `flutter_background_service` did not support. + +Thanks to [nschairer](https://github.com/nschairer) for [flutter_keyboard_height](https://github.com/nschairer/keyboard_height_plugin), which was the base for keeping track of the keyboard height. +Due to having an issue with the height calculation if the Android device uses gesture navigation, I +[forked the package](https://git.polynom.me/moxxy/keyboard_height_plugin) and modified the height calculation. diff --git a/android/src/main/kotlin/org/moxxy/moxxy_native/Constants.kt b/android/src/main/kotlin/org/moxxy/moxxy_native/Constants.kt index cb4c290..81a5348 100644 --- a/android/src/main/kotlin/org/moxxy/moxxy_native/Constants.kt +++ b/android/src/main/kotlin/org/moxxy/moxxy_native/Constants.kt @@ -2,6 +2,9 @@ package org.moxxy.moxxy_native const val TAG = "moxxy_native" +// The event channel name for the keyboard height +const val KEYBOARD_HEIGHT_EVENT_CHANNEL_NAME = "org.moxxy.moxxyv2/notification_stream" + // The size of buffers to use for various operations const val BUFFER_SIZE = 4096 diff --git a/android/src/main/kotlin/org/moxxy/moxxy_native/MoxxyNativePlugin.kt b/android/src/main/kotlin/org/moxxy/moxxy_native/MoxxyNativePlugin.kt index 0dd27db..0065eef 100644 --- a/android/src/main/kotlin/org/moxxy/moxxy_native/MoxxyNativePlugin.kt +++ b/android/src/main/kotlin/org/moxxy/moxxy_native/MoxxyNativePlugin.kt @@ -29,6 +29,7 @@ import org.moxxy.moxxy_native.notifications.NotificationsImplementation import org.moxxy.moxxy_native.picker.FilePickerType import org.moxxy.moxxy_native.picker.MoxxyPickerApi import org.moxxy.moxxy_native.picker.PickerResultListener +import org.moxxy.moxxy_native.platform.KeyboardStreamHandler import org.moxxy.moxxy_native.platform.MoxxyPlatformApi import org.moxxy.moxxy_native.platform.PlatformImplementation import org.moxxy.moxxy_native.service.BackgroundService @@ -102,6 +103,10 @@ class MoxxyNativePlugin : FlutterPlugin, ActivityAware, ServiceAware, BroadcastR IntentFilter(SERVICE_FOREGROUND_METHOD_CHANNEL_KEY), ) + // Special handling for the keyboard height + val keyboardChannel = EventChannel(flutterPluginBinding.getBinaryMessenger(), KEYBOARD_HEIGHT_EVENT_CHANNEL_NAME) + keyboardChannel?.setStreamHandler(KeyboardStreamHandler) + // Register the picker handler pickerListener = PickerResultListener(context!!) Log.d(TAG, "Attached to engine") @@ -118,20 +123,24 @@ class MoxxyNativePlugin : FlutterPlugin, ActivityAware, ServiceAware, BroadcastR override fun onAttachedToActivity(binding: ActivityPluginBinding) { activity = binding.activity binding.addActivityResultListener(pickerListener) + KeyboardStreamHandler.activity = activity Log.d(TAG, "Attached to activity") } override fun onDetachedFromActivityForConfigChanges() { activity = null + KeyboardStreamHandler.activity = null Log.d(TAG, "Detached from activity") } override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { activity = binding.activity + KeyboardStreamHandler.activity = activity } override fun onDetachedFromActivity() { activity = null + KeyboardStreamHandler.activity = null Log.d(TAG, "Detached from activity") } diff --git a/android/src/main/kotlin/org/moxxy/moxxy_native/notifications/NotificationReceiver.kt b/android/src/main/kotlin/org/moxxy/moxxy_native/notifications/NotificationReceiver.kt index 21a22ab..9f8a47c 100644 --- a/android/src/main/kotlin/org/moxxy/moxxy_native/notifications/NotificationReceiver.kt +++ b/android/src/main/kotlin/org/moxxy/moxxy_native/notifications/NotificationReceiver.kt @@ -164,7 +164,7 @@ class NotificationReceiver : BroadcastReceiver() { } } - fun handleTap(context: Context, intent: Intent) { + private fun handleTap(context: Context, intent: Intent) { MoxxyEventChannels.notificationEventSink?.success( NotificationEvent( intent.getLongExtra(NOTIFICATION_EXTRA_ID_KEY, -1), diff --git a/android/src/main/kotlin/org/moxxy/moxxy_native/platform/KeyboardStreamHandler.kt b/android/src/main/kotlin/org/moxxy/moxxy_native/platform/KeyboardStreamHandler.kt new file mode 100644 index 0000000..1a28221 --- /dev/null +++ b/android/src/main/kotlin/org/moxxy/moxxy_native/platform/KeyboardStreamHandler.kt @@ -0,0 +1,73 @@ +package org.moxxy.moxxy_native.platform + +import android.app.Activity +import android.graphics.Rect +import android.util.Log +import android.view.View +import android.view.ViewTreeObserver +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import io.flutter.plugin.common.EventChannel +import org.moxxy.moxxy_native.MoxxyEventChannels +import org.moxxy.moxxy_native.TAG + +object KeyboardStreamHandler : EventChannel.StreamHandler { + // The currently active activity. Set by @MoxxyNativePlugin. + var activity: Activity? = null + + // The current bottom inset. + private var bottomInset: Int = 0 + + // The current event sink to use for sending events to the UI. + private var sink: EventChannel.EventSink? = null + + private fun handleKeyboardHeightCheck(rootView: View?) { + rootView?.viewTreeObserver?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + val r = Rect() + rootView.getWindowVisibleDisplayFrame(r) + + val screenHeight = rootView.height + // Also subtract the height of the bottom inset as the SafeArea with "bottom: false" + // allows us to draw under the bottom system bar, if it is there. + val keypadHeight = screenHeight - r.bottom - bottomInset + + val displayMetrics = activity?.resources?.displayMetrics + val logicalKeypadHeight = keypadHeight / (displayMetrics?.density ?: 1f) + + if (keypadHeight > screenHeight * 0.15) { + sink?.success(logicalKeypadHeight.toDouble()) + } else { + sink?.success(0.0) + } + } + }) + } + + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + // "register" the event sink + sink = events + + val rootView = activity?.window?.decorView?.rootView + handleKeyboardHeightCheck(rootView) + + if (rootView != null) { + ViewCompat.setOnApplyWindowInsetsListener(rootView!!) { _, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + val triggerEvent = bottomInset != insets.bottom + bottomInset = insets.bottom + + // Notify in case the inset changed + if (triggerEvent) handleKeyboardHeightCheck(rootView) + + WindowInsetsCompat.CONSUMED + } + } + Log.d(TAG, "KeyboardStreamHandler: Attached stream") + } + + override fun onCancel(arguments: Any?) { + MoxxyEventChannels.notificationEventSink = null + Log.d(TAG, "KeyboardStreamHandler: Detached stream") + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index b96455e..26ded9c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,5 @@ // ignore_for_file: avoid_print import 'dart:io'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get_it/get_it.dart'; @@ -51,6 +50,19 @@ class TestEvent extends BackgroundEvent { class MyAppState extends State { String? imagePath; + @override + void initState() { + super.initState(); + + const EventChannel('org.moxxy.moxxyv2/notification_stream') + .receiveBroadcastStream() + .listen( + (event) { + print('Keyboard height: ${event as double}'); + }, + ); + } + @override Widget build(BuildContext context) { return MaterialApp( @@ -221,6 +233,7 @@ class MyAppState extends State { }, child: const Text('Share some text'), ), + const TextField(), ], ), ),