diff --git a/.github/workflows/pr_validation.yaml b/.github/workflows/pr_validation.yaml
index 50b8f9cb4a..7d0a58a4e3 100644
--- a/.github/workflows/pr_validation.yaml
+++ b/.github/workflows/pr_validation.yaml
@@ -137,6 +137,47 @@ jobs:
# Run all tests
- run: flutter test
+ analyze_super_keyboard:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./super_keyboard
+ steps:
+ # Checkout the PR branch
+ - uses: actions/checkout@v3
+
+ # Setup Flutter environment
+ - uses: subosito/flutter-action@v2
+ with:
+ channel: "master"
+
+ # Download all the packages that the app uses
+ - run: flutter pub get
+
+ # Enforce static analysis
+ - run: flutter analyze
+
+ test_super_keyboard:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./super_keyboard
+ steps:
+ # Checkout the PR branch
+ - uses: actions/checkout@v3
+
+ # Setup Flutter environment
+ - uses: subosito/flutter-action@v2
+ with:
+ channel: "master"
+
+ # Download all the packages that the app uses
+ - run: flutter pub get
+
+ # Run all tests
+ - run: flutter test
+
+
analyze_super_text_layout:
runs-on: ubuntu-latest
defaults:
diff --git a/super_keyboard/.gitignore b/super_keyboard/.gitignore
new file mode 100644
index 0000000000..ac5aa9893e
--- /dev/null
+++ b/super_keyboard/.gitignore
@@ -0,0 +1,29 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+build/
diff --git a/super_keyboard/.metadata b/super_keyboard/.metadata
new file mode 100644
index 0000000000..bf1e3f94b9
--- /dev/null
+++ b/super_keyboard/.metadata
@@ -0,0 +1,36 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819"
+ channel: "[user-branch]"
+
+project_type: plugin
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
+ base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
+ - platform: android
+ create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
+ base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
+ - platform: ios
+ create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
+ base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
+ - platform: web
+ create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
+ base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/super_keyboard/CHANGELOG.md b/super_keyboard/CHANGELOG.md
new file mode 100644
index 0000000000..2cdc64c730
--- /dev/null
+++ b/super_keyboard/CHANGELOG.md
@@ -0,0 +1,4 @@
+## [0.1.0] - [DATE]
+Initial release:
+ * iOS: Reports keyboard closed, opening, open, and closing. No keyboard height.
+ * Android: Reports keyboard closed, opening, open, and closing, as well as keyboard height.
diff --git a/super_keyboard/LICENSE b/super_keyboard/LICENSE
new file mode 100644
index 0000000000..df5ad7ba31
--- /dev/null
+++ b/super_keyboard/LICENSE
@@ -0,0 +1,7 @@
+Copyright (c) 2021 Superlist, SuperDeclarative! and the contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/super_keyboard/README.md b/super_keyboard/README.md
new file mode 100644
index 0000000000..a227a94ab0
--- /dev/null
+++ b/super_keyboard/README.md
@@ -0,0 +1,57 @@
+# Super Keyboard
+A plugin that reports keyboard visibility and size.
+
+## Support Platforms
+This plugin supports iOS and Android.
+
+## Unified API
+For users that don't care about differences between how iOS and Android report
+keyboard information, the easiest way to use `super_keyboard` is through the
+unified (lowest common denominator) API.
+
+Build a widget subtree based on the keyboard state:
+```dart
+@override
+Widget build(BuildContext context) {
+ return SuperKeyboardBuilder(
+ builder: (context, keyboardState) {
+ // TODO: do something with the keyboard state.
+ return const SizedBox();
+ }
+ );
+}
+```
+
+Directly listen for changes to the keyboard state:
+```dart
+void startListeningToKeyboardState() {
+ SuperKeyboard.instance.state.addListener(_onKeyboardStateChange);
+}
+
+void stopListeningToKeyboardState() {
+ SuperKeyboard.instance.state.removeListener(_onKeyboardStateChange);
+}
+
+void _onKeyboardStateChange(KeyboardState newState) {
+ // TODO: do something with the new keyboard state.
+}
+```
+
+Activate logs:
+```dart
+SuperKeyboard.initLogs();
+```
+
+## iOS and Android
+Platform-specific APIs are also available. The unified `SuperKeyboard` API
+delegates to the platform-specific APIs under the hood.
+
+iOS is available in `SuperKeyboardIOS`.
+
+Android is available in `SuperKeyboardAndroid`.
+
+Per-platform APIs are made available because each platform reports keyboard
+state and height in different ways. Those reporting methods may, or may not
+be compatible with each in general. Also, one platform might report more
+keyboard information than the other. We want to provide the maximum information
+possible to users, which can't be done with a lowest common denominator API.
\ No newline at end of file
diff --git a/super_keyboard/analysis_options.yaml b/super_keyboard/analysis_options.yaml
new file mode 100644
index 0000000000..a5744c1cfb
--- /dev/null
+++ b/super_keyboard/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/super_keyboard/android/.gitignore b/super_keyboard/android/.gitignore
new file mode 100644
index 0000000000..161bdcdaf8
--- /dev/null
+++ b/super_keyboard/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/super_keyboard/android/build.gradle b/super_keyboard/android/build.gradle
new file mode 100644
index 0000000000..4b451c2dfd
--- /dev/null
+++ b/super_keyboard/android/build.gradle
@@ -0,0 +1,68 @@
+group = "com.flutterbountyhunters.superkeyboard.super_keyboard"
+version = "1.0-SNAPSHOT"
+
+buildscript {
+ ext.kotlin_version = "1.7.10"
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath("com.android.tools.build:gradle:7.3.0")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+apply plugin: "com.android.library"
+apply plugin: "kotlin-android"
+
+android {
+ if (project.android.hasProperty("namespace")) {
+ namespace = "com.flutterbountyhunters.superkeyboard.super_keyboard"
+ }
+
+ compileSdk = 34
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_1_8
+ }
+
+ sourceSets {
+ main.java.srcDirs += "src/main/kotlin"
+ test.java.srcDirs += "src/test/kotlin"
+ }
+
+ defaultConfig {
+ minSdk = 21
+ }
+
+ dependencies {
+ testImplementation("org.jetbrains.kotlin:kotlin-test")
+ testImplementation("org.mockito:mockito-core:5.0.0")
+ }
+
+ testOptions {
+ unitTests.all {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed", "standardOut", "standardError"
+ outputs.upToDateWhen {false}
+ showStandardStreams = true
+ }
+ }
+ }
+}
diff --git a/super_keyboard/android/settings.gradle b/super_keyboard/android/settings.gradle
new file mode 100644
index 0000000000..c49919a88b
--- /dev/null
+++ b/super_keyboard/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'super_keyboard'
diff --git a/super_keyboard/android/src/main/AndroidManifest.xml b/super_keyboard/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..545ea100f9
--- /dev/null
+++ b/super_keyboard/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/super_keyboard/android/src/main/kotlin/com/flutterbountyhunters/superkeyboard/super_keyboard/SuperKeyboardPlugin.kt b/super_keyboard/android/src/main/kotlin/com/flutterbountyhunters/superkeyboard/super_keyboard/SuperKeyboardPlugin.kt
new file mode 100644
index 0000000000..5188f7e241
--- /dev/null
+++ b/super_keyboard/android/src/main/kotlin/com/flutterbountyhunters/superkeyboard/super_keyboard/SuperKeyboardPlugin.kt
@@ -0,0 +1,148 @@
+package com.flutterbountyhunters.superkeyboard.super_keyboard
+
+import android.app.Activity
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import androidx.core.view.OnApplyWindowInsetsListener
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsAnimationCompat
+import androidx.core.view.WindowInsetsCompat
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.embedding.engine.plugins.activity.ActivityAware
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
+import io.flutter.plugin.common.MethodChannel
+
+
+/**
+ * Plugin that reports software keyboard state changes, and (maybe) keyboard height changes.
+ *
+ * Tracking keyboard height is more difficult. There may be some platforms that don't
+ * support height tracking.
+ *
+ * Android Docs: https://developer.android.com/develop/ui/views/layout/sw-keyboard
+ */
+class SuperKeyboardPlugin: FlutterPlugin, ActivityAware, OnApplyWindowInsetsListener {
+ private lateinit var channel : MethodChannel
+
+ // The root view within the Android Activity.
+ private var mainView: View? = null
+
+ // The manager for text input for the Android Activity.
+ private lateinit var ime: InputMethodManager
+
+ // The most recent known state of the software keyboard.
+ private var keyboardState: KeyboardState = KeyboardState.Closed
+
+ override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+ channel = MethodChannel(flutterPluginBinding.binaryMessenger, "super_keyboard_android")
+ }
+
+ override fun onAttachedToActivity(binding: ActivityPluginBinding) {
+ startListeningForKeyboardChanges(binding.activity)
+ }
+
+ override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
+ startListeningForKeyboardChanges(binding.activity)
+ }
+
+ private fun startListeningForKeyboardChanges(activity: Activity) {
+ mainView = activity.findViewById(android.R.id.content)
+ ime = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
+ if (mainView == null) {
+ // This should never happen. If it does, we just fizzle.
+ return;
+ }
+
+ // Track keyboard opening and closing.
+ ViewCompat.setOnApplyWindowInsetsListener(mainView!!, this)
+
+ // Track keyboard fully open, fully closed, and height.
+ ViewCompat.setWindowInsetsAnimationCallback(
+ mainView!!,
+ object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
+ override fun onPrepare(
+ animation: WindowInsetsAnimationCompat
+ ) {
+ // no-op
+ }
+
+ override fun onStart(
+ animation: WindowInsetsAnimationCompat,
+ bounds: WindowInsetsAnimationCompat.BoundsCompat
+ ): WindowInsetsAnimationCompat.BoundsCompat {
+ // no-op
+ return bounds
+ }
+
+ override fun onProgress(
+ insets: WindowInsetsCompat,
+ runningAnimations: MutableList
+ ): WindowInsetsCompat {
+ val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
+
+ channel.invokeMethod("onProgress", mapOf(
+ "keyboardHeight" to imeHeight,
+ ))
+
+ return insets
+ }
+
+ override fun onEnd(
+ animation: WindowInsetsAnimationCompat
+ ) {
+ // Report whether the keyboard has fully opened or fully closed.
+ if (keyboardState == KeyboardState.Opening) {
+ channel.invokeMethod("keyboardOpened", null)
+ } else if (keyboardState == KeyboardState.Closing) {
+ channel.invokeMethod("keyboardClosed", null)
+ }
+ }
+ }
+ )
+ }
+
+ override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
+ val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
+
+ // Note: We only identify opening/closing here. The opened/closed completion
+ // is identified by the window insets animation callback.
+ if (imeVisible && keyboardState != KeyboardState.Opening && keyboardState != KeyboardState.Open) {
+ channel.invokeMethod("keyboardOpening", null)
+ keyboardState = KeyboardState.Opening
+ } else if (!imeVisible && keyboardState != KeyboardState.Closing && keyboardState != KeyboardState.Closed) {
+ channel.invokeMethod("keyboardClosing", null)
+ keyboardState = KeyboardState.Closing
+ }
+
+ return insets
+ }
+
+ override fun onDetachedFromActivityForConfigChanges() {
+ stopListeningForKeyboardChanges()
+ }
+
+ override fun onDetachedFromActivity() {
+ stopListeningForKeyboardChanges()
+ }
+
+ private fun stopListeningForKeyboardChanges() {
+ if (mainView == null) {
+ return;
+ }
+
+ ViewCompat.setOnApplyWindowInsetsListener(mainView!!, null)
+ ViewCompat.setWindowInsetsAnimationCallback(mainView!!, null)
+
+ mainView = null
+ }
+
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {}
+}
+
+private enum class KeyboardState {
+ Closed,
+ Opening,
+ Open,
+ Closing;
+}
\ No newline at end of file
diff --git a/super_keyboard/example/.gitignore b/super_keyboard/example/.gitignore
new file mode 100644
index 0000000000..29a3a5017f
--- /dev/null
+++ b/super_keyboard/example/.gitignore
@@ -0,0 +1,43 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/super_keyboard/example/README.md b/super_keyboard/example/README.md
new file mode 100644
index 0000000000..30ebd3be05
--- /dev/null
+++ b/super_keyboard/example/README.md
@@ -0,0 +1,3 @@
+# Super Keyboard Example
+
+Demonstrates how to use the super_keyboard plugin.
diff --git a/super_keyboard/example/analysis_options.yaml b/super_keyboard/example/analysis_options.yaml
new file mode 100644
index 0000000000..0d2902135c
--- /dev/null
+++ b/super_keyboard/example/analysis_options.yaml
@@ -0,0 +1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/super_keyboard/example/android/.gitignore b/super_keyboard/example/android/.gitignore
new file mode 100644
index 0000000000..55afd919c6
--- /dev/null
+++ b/super_keyboard/example/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/to/reference-keystore
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/super_keyboard/example/android/app/build.gradle b/super_keyboard/example/android/app/build.gradle
new file mode 100644
index 0000000000..a12bd68c61
--- /dev/null
+++ b/super_keyboard/example/android/app/build.gradle
@@ -0,0 +1,44 @@
+plugins {
+ id "com.android.application"
+ id "kotlin-android"
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id "dev.flutter.flutter-gradle-plugin"
+}
+
+android {
+ namespace = "com.flutterbountyhunters.superkeyboard.super_keyboard_example"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_1_8
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "com.flutterbountyhunters.superkeyboard.super_keyboard_example"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/super_keyboard/example/android/app/src/debug/AndroidManifest.xml b/super_keyboard/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000000..399f6981d5
--- /dev/null
+++ b/super_keyboard/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/super_keyboard/example/android/app/src/main/AndroidManifest.xml b/super_keyboard/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..9c0637d21e
--- /dev/null
+++ b/super_keyboard/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/super_keyboard/example/android/app/src/main/kotlin/com/flutterbountyhunters/superkeyboard/super_keyboard_example/MainActivity.kt b/super_keyboard/example/android/app/src/main/kotlin/com/flutterbountyhunters/superkeyboard/super_keyboard_example/MainActivity.kt
new file mode 100644
index 0000000000..309b8269ff
--- /dev/null
+++ b/super_keyboard/example/android/app/src/main/kotlin/com/flutterbountyhunters/superkeyboard/super_keyboard_example/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.flutterbountyhunters.superkeyboard.super_keyboard_example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity()
diff --git a/super_keyboard/example/android/app/src/main/res/drawable-v21/launch_background.xml b/super_keyboard/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000000..f74085f3f6
--- /dev/null
+++ b/super_keyboard/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/super_keyboard/example/android/app/src/main/res/drawable/launch_background.xml b/super_keyboard/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000000..304732f884
--- /dev/null
+++ b/super_keyboard/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/super_keyboard/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/super_keyboard/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..db77bb4b7b
Binary files /dev/null and b/super_keyboard/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/super_keyboard/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/super_keyboard/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..17987b79bb
Binary files /dev/null and b/super_keyboard/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/super_keyboard/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/super_keyboard/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..09d4391482
Binary files /dev/null and b/super_keyboard/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/super_keyboard/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/super_keyboard/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..d5f1c8d34e
Binary files /dev/null and b/super_keyboard/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/super_keyboard/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/super_keyboard/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..4d6372eebd
Binary files /dev/null and b/super_keyboard/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/super_keyboard/example/android/app/src/main/res/values-night/styles.xml b/super_keyboard/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000000..06952be745
--- /dev/null
+++ b/super_keyboard/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/super_keyboard/example/android/app/src/main/res/values/styles.xml b/super_keyboard/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000000..cb1ef88056
--- /dev/null
+++ b/super_keyboard/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/super_keyboard/example/android/app/src/profile/AndroidManifest.xml b/super_keyboard/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000000..399f6981d5
--- /dev/null
+++ b/super_keyboard/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/super_keyboard/example/android/build.gradle b/super_keyboard/example/android/build.gradle
new file mode 100644
index 0000000000..d2ffbffa4c
--- /dev/null
+++ b/super_keyboard/example/android/build.gradle
@@ -0,0 +1,18 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.buildDir = "../build"
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean", Delete) {
+ delete rootProject.buildDir
+}
diff --git a/super_keyboard/example/android/gradle.properties b/super_keyboard/example/android/gradle.properties
new file mode 100644
index 0000000000..2597170821
--- /dev/null
+++ b/super_keyboard/example/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/super_keyboard/example/android/gradle/wrapper/gradle-wrapper.properties b/super_keyboard/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..09523c0e54
--- /dev/null
+++ b/super_keyboard/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/super_keyboard/example/android/settings.gradle b/super_keyboard/example/android/settings.gradle
new file mode 100644
index 0000000000..536165d35a
--- /dev/null
+++ b/super_keyboard/example/android/settings.gradle
@@ -0,0 +1,25 @@
+pluginManagement {
+ def flutterSdkPath = {
+ def properties = new Properties()
+ file("local.properties").withInputStream { properties.load(it) }
+ def flutterSdkPath = properties.getProperty("flutter.sdk")
+ assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+ return flutterSdkPath
+ }()
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id "dev.flutter.flutter-plugin-loader" version "1.0.0"
+ id "com.android.application" version "7.3.0" apply false
+ id "org.jetbrains.kotlin.android" version "1.7.10" apply false
+}
+
+include ":app"
diff --git a/super_keyboard/example/ios/.gitignore b/super_keyboard/example/ios/.gitignore
new file mode 100644
index 0000000000..7a7f9873ad
--- /dev/null
+++ b/super_keyboard/example/ios/.gitignore
@@ -0,0 +1,34 @@
+**/dgph
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/super_keyboard/example/ios/Flutter/AppFrameworkInfo.plist b/super_keyboard/example/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000000..7c56964006
--- /dev/null
+++ b/super_keyboard/example/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 12.0
+
+
diff --git a/super_keyboard/example/ios/Flutter/Debug.xcconfig b/super_keyboard/example/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000000..ec97fc6f30
--- /dev/null
+++ b/super_keyboard/example/ios/Flutter/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "Generated.xcconfig"
diff --git a/super_keyboard/example/ios/Flutter/Release.xcconfig b/super_keyboard/example/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000000..c4855bfe20
--- /dev/null
+++ b/super_keyboard/example/ios/Flutter/Release.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "Generated.xcconfig"
diff --git a/super_keyboard/example/ios/Podfile b/super_keyboard/example/ios/Podfile
new file mode 100644
index 0000000000..d97f17e223
--- /dev/null
+++ b/super_keyboard/example/ios/Podfile
@@ -0,0 +1,44 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '12.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+ end
+end
diff --git a/super_keyboard/example/ios/Podfile.lock b/super_keyboard/example/ios/Podfile.lock
new file mode 100644
index 0000000000..85616d72f6
--- /dev/null
+++ b/super_keyboard/example/ios/Podfile.lock
@@ -0,0 +1,28 @@
+PODS:
+ - Flutter (1.0.0)
+ - integration_test (0.0.1):
+ - Flutter
+ - super_keyboard (0.0.1):
+ - Flutter
+
+DEPENDENCIES:
+ - Flutter (from `Flutter`)
+ - integration_test (from `.symlinks/plugins/integration_test/ios`)
+ - super_keyboard (from `.symlinks/plugins/super_keyboard/ios`)
+
+EXTERNAL SOURCES:
+ Flutter:
+ :path: Flutter
+ integration_test:
+ :path: ".symlinks/plugins/integration_test/ios"
+ super_keyboard:
+ :path: ".symlinks/plugins/super_keyboard/ios"
+
+SPEC CHECKSUMS:
+ Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
+ super_keyboard: 8e7a8c2e2499f32476103eb3be4069f2545fc92a
+
+PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
+
+COCOAPODS: 1.14.2
diff --git a/super_keyboard/example/ios/Runner.xcodeproj/project.pbxproj b/super_keyboard/example/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000..a1c85c8eac
--- /dev/null
+++ b/super_keyboard/example/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,728 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 54;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 85B39FB580D675688835B438 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C1E1F2F8D24A1D5DD1C5264 /* Pods_Runner.framework */; };
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+ A12D3A9BB66DF164974571B4 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4EFDA78E32E712C5D1BE2F0A /* Pods_RunnerTests.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 97C146ED1CF9000F007C117D;
+ remoteInfo = Runner;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 10B9FDA75586173963B8EB8F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 44FFA1350DD6D19D2ECB809C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
+ 4EFDA78E32E712C5D1BE2F0A /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 72A070FD9738D482E839119E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 78AA13D825ACBF093F6C31E1 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 9C1E1F2F8D24A1D5DD1C5264 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ BC74956F75DE51149BFF0386 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
+ BC9B897EB3C25830A18E35C1 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 07CE555E0179B6199470FE20 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ A12D3A9BB66DF164974571B4 /* Pods_RunnerTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 85B39FB580D675688835B438 /* Pods_Runner.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 30E634ACC6EAFA4531FC9419 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 72A070FD9738D482E839119E /* Pods-Runner.debug.xcconfig */,
+ 78AA13D825ACBF093F6C31E1 /* Pods-Runner.release.xcconfig */,
+ 10B9FDA75586173963B8EB8F /* Pods-Runner.profile.xcconfig */,
+ BC9B897EB3C25830A18E35C1 /* Pods-RunnerTests.debug.xcconfig */,
+ 44FFA1350DD6D19D2ECB809C /* Pods-RunnerTests.release.xcconfig */,
+ BC74956F75DE51149BFF0386 /* Pods-RunnerTests.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
+ 331C8082294A63A400263BE5 /* RunnerTests */ = {
+ isa = PBXGroup;
+ children = (
+ 331C807B294A618700263BE5 /* RunnerTests.swift */,
+ );
+ path = RunnerTests;
+ sourceTree = "";
+ };
+ 9740EEB11CF90186004384FC /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
+ );
+ name = Flutter;
+ sourceTree = "";
+ };
+ 97C146E51CF9000F007C117D = {
+ isa = PBXGroup;
+ children = (
+ 9740EEB11CF90186004384FC /* Flutter */,
+ 97C146F01CF9000F007C117D /* Runner */,
+ 97C146EF1CF9000F007C117D /* Products */,
+ 331C8082294A63A400263BE5 /* RunnerTests */,
+ 30E634ACC6EAFA4531FC9419 /* Pods */,
+ CF5F937805FA43FD2F5BFF1C /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 97C146F01CF9000F007C117D /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146FA1CF9000F007C117D /* Main.storyboard */,
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */,
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+ 97C147021CF9000F007C117D /* Info.plist */,
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+ CF5F937805FA43FD2F5BFF1C /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 9C1E1F2F8D24A1D5DD1C5264 /* Pods_Runner.framework */,
+ 4EFDA78E32E712C5D1BE2F0A /* Pods_RunnerTests.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 331C8080294A63A400263BE5 /* RunnerTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+ buildPhases = (
+ A9F352D0FA2204A94AC87F0A /* [CP] Check Pods Manifest.lock */,
+ 331C807D294A63A400263BE5 /* Sources */,
+ 331C807F294A63A400263BE5 /* Resources */,
+ 07CE555E0179B6199470FE20 /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 331C8086294A63A400263BE5 /* PBXTargetDependency */,
+ );
+ name = RunnerTests;
+ productName = RunnerTests;
+ productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 97C146ED1CF9000F007C117D /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ 7781B65B4C30BAD292E9A9CF /* [CP] Check Pods Manifest.lock */,
+ 9740EEB61CF901F6004384FC /* Run Script */,
+ 97C146EA1CF9000F007C117D /* Sources */,
+ 97C146EB1CF9000F007C117D /* Frameworks */,
+ 97C146EC1CF9000F007C117D /* Resources */,
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ EE3FF589CBC6C9962CFF0BD1 /* [CP] Embed Pods Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 97C146E61CF9000F007C117D /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = YES;
+ LastUpgradeCheck = 1510;
+ ORGANIZATIONNAME = "";
+ TargetAttributes = {
+ 331C8080294A63A400263BE5 = {
+ CreatedOnToolsVersion = 14.0;
+ TestTargetID = 97C146ED1CF9000F007C117D;
+ };
+ 97C146ED1CF9000F007C117D = {
+ CreatedOnToolsVersion = 7.3.1;
+ LastSwiftMigration = 1100;
+ };
+ };
+ };
+ buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 97C146E51CF9000F007C117D;
+ productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 97C146ED1CF9000F007C117D /* Runner */,
+ 331C8080294A63A400263BE5 /* RunnerTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 331C807F294A63A400263BE5 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 97C146EC1CF9000F007C117D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
+ );
+ name = "Thin Binary";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+ };
+ 7781B65B4C30BAD292E9A9CF /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Run Script";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+ };
+ A9F352D0FA2204A94AC87F0A /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ EE3FF589CBC6C9962CFF0BD1 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 331C807D294A63A400263BE5 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 97C146EA1CF9000F007C117D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 97C146ED1CF9000F007C117D /* Runner */;
+ targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C146FB1CF9000F007C117D /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C147001CF9000F007C117D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 249021D3217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Profile;
+ };
+ 249021D4217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.flutterbountyhunters.superkeyboard.superKeyboardExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Profile;
+ };
+ 331C8088294A63A400263BE5 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = BC9B897EB3C25830A18E35C1 /* Pods-RunnerTests.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.flutterbountyhunters.superkeyboard.superKeyboardExample.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Debug;
+ };
+ 331C8089294A63A400263BE5 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 44FFA1350DD6D19D2ECB809C /* Pods-RunnerTests.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.flutterbountyhunters.superkeyboard.superKeyboardExample.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Release;
+ };
+ 331C808A294A63A400263BE5 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = BC74956F75DE51149BFF0386 /* Pods-RunnerTests.profile.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.flutterbountyhunters.superkeyboard.superKeyboardExample.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Profile;
+ };
+ 97C147031CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 97C147041CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 97C147061CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.flutterbountyhunters.superkeyboard.superKeyboardExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Debug;
+ };
+ 97C147071CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.flutterbountyhunters.superkeyboard.superKeyboardExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 331C8088294A63A400263BE5 /* Debug */,
+ 331C8089294A63A400263BE5 /* Release */,
+ 331C808A294A63A400263BE5 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147031CF9000F007C117D /* Debug */,
+ 97C147041CF9000F007C117D /* Release */,
+ 249021D3217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147061CF9000F007C117D /* Debug */,
+ 97C147071CF9000F007C117D /* Release */,
+ 249021D4217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/super_keyboard/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/super_keyboard/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000000..919434a625
--- /dev/null
+++ b/super_keyboard/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/super_keyboard/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/super_keyboard/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000000..18d981003d
--- /dev/null
+++ b/super_keyboard/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/super_keyboard/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/super_keyboard/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000000..f9b0d7c5ea
--- /dev/null
+++ b/super_keyboard/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/super_keyboard/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/super_keyboard/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000000..8e3ca5dfe1
--- /dev/null
+++ b/super_keyboard/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/super_keyboard/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/super_keyboard/example/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000000..21a3cc14c7
--- /dev/null
+++ b/super_keyboard/example/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/super_keyboard/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/super_keyboard/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000000..18d981003d
--- /dev/null
+++ b/super_keyboard/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/super_keyboard/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/super_keyboard/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000000..f9b0d7c5ea
--- /dev/null
+++ b/super_keyboard/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/super_keyboard/example/ios/Runner/AppDelegate.swift b/super_keyboard/example/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000000..626664468b
--- /dev/null
+++ b/super_keyboard/example/ios/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+import Flutter
+import UIKit
+
+@main
+@objc class AppDelegate: FlutterAppDelegate {
+ override func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+ ) -> Bool {
+ GeneratedPluginRegistrant.register(with: self)
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+ }
+}
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000000..d36b1fab2d
--- /dev/null
+++ b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+ "images" : [
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-83.5x83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "Icon-App-1024x1024@1x.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000000..dc9ada4725
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000000..7353c41ecf
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000000..797d452e45
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000000..6ed2d933e1
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000000..4cd7b0099c
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000000..fe730945a0
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000000..321773cd85
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000000..797d452e45
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000000..502f463a9b
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000000..0ec3034392
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000000..0ec3034392
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000000..e9f5fea27c
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000000..84ac32ae7d
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000000..8953cba090
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000000..0467bf12aa
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000000..0bedcf2fd4
--- /dev/null
+++ b/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000000..9da19eacad
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000000..9da19eacad
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000000..9da19eacad
Binary files /dev/null and b/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000000..89c2725b70
--- /dev/null
+++ b/super_keyboard/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/super_keyboard/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/super_keyboard/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000000..f2e259c7c9
--- /dev/null
+++ b/super_keyboard/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/super_keyboard/example/ios/Runner/Base.lproj/Main.storyboard b/super_keyboard/example/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000000..f3c28516fb
--- /dev/null
+++ b/super_keyboard/example/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/super_keyboard/example/ios/Runner/Info.plist b/super_keyboard/example/ios/Runner/Info.plist
new file mode 100644
index 0000000000..366c03aa3f
--- /dev/null
+++ b/super_keyboard/example/ios/Runner/Info.plist
@@ -0,0 +1,49 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Super Keyboard
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ super_keyboard_example
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+
+
diff --git a/super_keyboard/example/ios/Runner/Runner-Bridging-Header.h b/super_keyboard/example/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000000..308a2a560b
--- /dev/null
+++ b/super_keyboard/example/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/super_keyboard/example/ios/RunnerTests/RunnerTests.swift b/super_keyboard/example/ios/RunnerTests/RunnerTests.swift
new file mode 100644
index 0000000000..5cc9c0e27f
--- /dev/null
+++ b/super_keyboard/example/ios/RunnerTests/RunnerTests.swift
@@ -0,0 +1,27 @@
+import Flutter
+import UIKit
+import XCTest
+
+
+@testable import super_keyboard
+
+// This demonstrates a simple unit test of the Swift portion of this plugin's implementation.
+//
+// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+
+class RunnerTests: XCTestCase {
+
+ func testGetPlatformVersion() {
+ let plugin = SuperKeyboardPlugin()
+
+ let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: [])
+
+ let resultExpectation = expectation(description: "result block must be called.")
+ plugin.handle(call) { result in
+ XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion)
+ resultExpectation.fulfill()
+ }
+ waitForExpectations(timeout: 1)
+ }
+
+}
diff --git a/super_keyboard/example/lib/main.dart b/super_keyboard/example/lib/main.dart
new file mode 100644
index 0000000000..ce064129b5
--- /dev/null
+++ b/super_keyboard/example/lib/main.dart
@@ -0,0 +1,103 @@
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:super_keyboard/super_keyboard.dart';
+
+void main() {
+ runApp(const MyApp());
+}
+
+class MyApp extends StatefulWidget {
+ const MyApp({super.key});
+
+ @override
+ State createState() => _MyAppState();
+}
+
+class _MyAppState extends State {
+ @override
+ void initState() {
+ super.initState();
+
+ initSuperKeyboard();
+ }
+
+ Future initSuperKeyboard() async {
+ SuperKeyboard.initLogs();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ home: Scaffold(
+ resizeToAvoidBottomInset: defaultTargetPlatform != TargetPlatform.android,
+ body: Center(
+ child: ConstrainedBox(
+ constraints: const BoxConstraints(maxWidth: 250),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ ValueListenableBuilder(
+ valueListenable: SuperKeyboard.instance.state,
+ builder: (context, value, child) {
+ final icon = switch (value) {
+ KeyboardState.closed => Icons.border_bottom,
+ KeyboardState.opening => Icons.upload_sharp,
+ KeyboardState.open => Icons.border_top,
+ KeyboardState.closing => Icons.download_sharp,
+ };
+
+ return Icon(
+ icon,
+ size: 24,
+ );
+ },
+ ),
+ const SizedBox(height: 12),
+ SuperKeyboardBuilder(builder: (context, keyboardState) {
+ return Text("Keyboard state: $_keyboardState");
+ }),
+ const SizedBox(height: 12),
+ ValueListenableBuilder(
+ valueListenable: SuperKeyboard.instance.keyboardHeight,
+ builder: (context, value, child) {
+ return Text("Keyboard height: ${value != null ? "${value.toInt()}" : "???"}");
+ },
+ ),
+ const SizedBox(height: 48),
+ TextField(
+ decoration: const InputDecoration(
+ hintText: "Type some text",
+ ),
+ onTapOutside: (_) {
+ FocusManager.instance.primaryFocus?.unfocus();
+ },
+ ),
+ ValueListenableBuilder(
+ valueListenable: SuperKeyboard.instance.keyboardHeight,
+ builder: (context, value, child) {
+ if (value == null) {
+ return const SizedBox();
+ }
+
+ return SizedBox(height: value / MediaQuery.of(context).devicePixelRatio);
+ },
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ String get _keyboardState {
+ return switch (SuperKeyboard.instance.state.value) {
+ KeyboardState.closed => "Closed",
+ KeyboardState.opening => "Opening",
+ KeyboardState.open => "Open",
+ KeyboardState.closing => "Closing",
+ };
+ }
+}
diff --git a/super_keyboard/example/pubspec.lock b/super_keyboard/example/pubspec.lock
new file mode 100644
index 0000000000..37f8e43869
--- /dev/null
+++ b/super_keyboard/example/pubspec.lock
@@ -0,0 +1,299 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.11.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.18.0"
+ cupertino_icons:
+ dependency: "direct main"
+ description:
+ name: cupertino_icons
+ sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.8"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.1"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.0"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_driver:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.0"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_test_runners:
+ dependency: transitive
+ description:
+ name: flutter_test_runners
+ sha256: cc575117ed66a79185a26995399d7048341517a1bd21188cb43753739627832d
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.0.4"
+ fuchsia_remote_debug_protocol:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ integration_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ leak_tracker:
+ dependency: transitive
+ description:
+ name: leak_tracker
+ sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
+ url: "https://pub.dev"
+ source: hosted
+ version: "10.0.5"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.5"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.1"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.0"
+ logging:
+ dependency: transitive
+ description:
+ name: logging
+ sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.16+1"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.11.1"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.15.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.9.0"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.5"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.8"
+ process:
+ dependency: transitive
+ description:
+ name: process
+ sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.2"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.99"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.0"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.11.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ super_keyboard:
+ dependency: "direct main"
+ description:
+ path: ".."
+ relative: true
+ source: path
+ version: "0.0.1"
+ sync_http:
+ dependency: transitive
+ description:
+ name: sync_http
+ sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.1"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.2"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
+ url: "https://pub.dev"
+ source: hosted
+ version: "14.2.4"
+ webdriver:
+ dependency: transitive
+ description:
+ name: webdriver
+ sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.3"
+sdks:
+ dart: ">=3.5.0 <4.0.0"
+ flutter: ">=3.18.0-18.0.pre.54"
diff --git a/super_keyboard/example/pubspec.yaml b/super_keyboard/example/pubspec.yaml
new file mode 100644
index 0000000000..01df7f78db
--- /dev/null
+++ b/super_keyboard/example/pubspec.yaml
@@ -0,0 +1,85 @@
+name: super_keyboard_example
+description: "Demonstrates how to use the super_keyboard plugin."
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+environment:
+ sdk: ^3.5.0
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+ flutter:
+ sdk: flutter
+
+ super_keyboard:
+ # When depending on this package from a real application you should use:
+ # super_keyboard: ^x.y.z
+ # See https://dart.dev/tools/pub/dependencies#version-constraints
+ # The example app is bundled with the plugin so we use a path dependency on
+ # the parent directory to use the current plugin's version.
+ path: ../
+
+ # The following adds the Cupertino Icons font to your application.
+ # Use with the CupertinoIcons class for iOS style icons.
+ cupertino_icons: ^1.0.8
+
+dev_dependencies:
+ integration_test:
+ sdk: flutter
+ flutter_test:
+ sdk: flutter
+
+ # The "flutter_lints" package below contains a set of recommended lints to
+ # encourage good coding practices. The lint set provided by the package is
+ # activated in the `analysis_options.yaml` file located at the root of your
+ # package. See that file for information about deactivating specific lint
+ # rules and activating additional ones.
+ flutter_lints: ^4.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+
+ # The following line ensures that the Material Icons font is
+ # included with your application, so that you can use the icons in
+ # the material Icons class.
+ uses-material-design: true
+
+ # To add assets to your application, add an assets section, like this:
+ # assets:
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/to/resolution-aware-images
+
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.dev/to/asset-from-package
+
+ # To add custom fonts to your application, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.dev/to/font-from-package
diff --git a/super_keyboard/example/test/widget_test.dart b/super_keyboard/example/test/widget_test.dart
new file mode 100644
index 0000000000..a16df62747
--- /dev/null
+++ b/super_keyboard/example/test/widget_test.dart
@@ -0,0 +1,27 @@
+// This is a basic Flutter widget test.
+//
+// To perform an interaction with a widget in your test, use the WidgetTester
+// utility in the flutter_test package. For example, you can send tap and scroll
+// gestures. You can also use WidgetTester to find child widgets in the widget
+// tree, read text, and verify that the values of widget properties are correct.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:super_keyboard_example/main.dart';
+
+void main() {
+ testWidgets('Verify Platform version', (WidgetTester tester) async {
+ // Build our app and trigger a frame.
+ await tester.pumpWidget(const MyApp());
+
+ // Verify that platform version is retrieved.
+ expect(
+ find.byWidgetPredicate(
+ (Widget widget) => widget is Text &&
+ widget.data!.startsWith('Running on:'),
+ ),
+ findsOneWidget,
+ );
+ });
+}
diff --git a/super_keyboard/ios/.gitignore b/super_keyboard/ios/.gitignore
new file mode 100644
index 0000000000..034771fc9c
--- /dev/null
+++ b/super_keyboard/ios/.gitignore
@@ -0,0 +1,38 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/Generated.xcconfig
+/Flutter/ephemeral/
+/Flutter/flutter_export_environment.sh
diff --git a/super_keyboard/ios/Assets/.gitkeep b/super_keyboard/ios/Assets/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/super_keyboard/ios/Classes/SuperKeyboardPlugin.swift b/super_keyboard/ios/Classes/SuperKeyboardPlugin.swift
new file mode 100644
index 0000000000..692a649833
--- /dev/null
+++ b/super_keyboard/ios/Classes/SuperKeyboardPlugin.swift
@@ -0,0 +1,184 @@
+import Flutter
+import UIKit
+
+public class SuperKeyboardPlugin: NSObject, FlutterPlugin {
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ let plugin = SuperKeyboardPlugin(binaryMessenger: registrar.messenger())
+ registrar.addMethodCallDelegate(plugin, channel: plugin.channel!)
+ }
+
+ private var channel: FlutterMethodChannel?
+
+// private var displayLink: CADisplayLink?
+ private weak var window: UIWindow?
+ private var keyboardType: KeyboardType = .unknown
+ private var keyboardFrame: CGRect = .zero
+// private var keyboardTimer: Timer?
+// private var isAnimating = false
+
+ init(binaryMessenger: FlutterBinaryMessenger) {
+ super.init()
+
+ channel = FlutterMethodChannel(name: "super_keyboard_ios", binaryMessenger: binaryMessenger)
+
+ // Register for keyboard notifications
+ NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(_:)), name: UIResponder.keyboardDidShowNotification, object: nil)
+
+ NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
+
+ NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidHide(_:)), name: UIResponder.keyboardDidHideNotification, object: nil)
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ switch call.method {
+ default:
+ result(FlutterMethodNotImplemented)
+ }
+ }
+
+ @objc private func keyboardWillShow(_ notification: Notification) {
+ channel!.invokeMethod("keyboardWillShow", arguments: nil)
+ }
+
+ @objc private func keyboardDidShow(_ notification: Notification) {
+ guard let window = window else {
+// stopTrackingKeyboard()
+ return
+ }
+
+ // Calculate the current keyboard height
+ let screenHeight = window.bounds.height
+ let keyboardHeight = max(0, screenHeight - keyboardFrame.origin.y)
+
+ channel!.invokeMethod("keyboardDidShow", arguments: [
+ "keyboard_height": keyboardHeight
+ ])
+ }
+
+ @objc private func keyboardWillChangeFrame(_ notification: Notification) {
+ guard let userInfo = notification.userInfo,
+ let endFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
+ return
+ }
+
+ // Set the final keyboard frame and track its position during animation
+ keyboardFrame = endFrame
+ window = UIApplication.shared.windows.first
+
+ switch keyboardFrame {
+ case let r where r.height <= 0:
+ keyboardType = .unknown
+ case let r where r.height < 100:
+ keyboardType = .minimized
+ default:
+ keyboardType = .full
+ }
+
+ channel!.invokeMethod("keyboardWillChangeFrame", arguments: [
+ "keyboardType": keyboardType.description,
+ "targetKeyboardHeight": keyboardFrame.height
+ ])
+
+// if (!isAnimating) {
+// startTrackingKeyboard(userInfo: userInfo)
+// }
+ }
+
+// private func startTrackingKeyboard(userInfo: [AnyHashable: Any]) {
+// print("startTrackingKeyboard()")
+// guard let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double,
+// let curveRawValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int else {
+// return
+// }
+//
+// let curve = UIView.AnimationCurve(rawValue: curveRawValue) ?? .easeInOut
+//
+// // Start a timer to poll the keyboard height at intervals
+// isAnimating = true
+// keyboardTimer = Timer.scheduledTimer(
+// timeInterval: 0.016, // Approximately 60 FPS
+// target: self,
+// selector: #selector(pollKeyboardHeight),
+// userInfo: nil,
+// repeats: true
+// )
+// }
+//
+// @objc private func pollKeyboardHeight() {
+// print("pollKeyboardHeight")
+// guard let window = window else {
+// stopTrackingKeyboard()
+// return
+// }
+//
+// // Calculate the current keyboard height
+// let screenHeight = window.bounds.height
+// let keyboardHeight = max(0, screenHeight - keyboardFrame.origin.y)
+// print("keyboardHeight: ", keyboardHeight, ", frame height: ", keyboardFrame.height)
+//
+// channel!.invokeMethod("keyboardGeometry", arguments: [
+// "y": keyboardFrame.origin.y,
+// "height": keyboardHeight
+// ])
+//
+// // Stop polling if the keyboard is fully open or hidden
+// print("Is height = 0? ", (keyboardHeight == 0))
+// if !isAnimating || keyboardHeight == 0 || keyboardHeight == keyboardFrame.height {
+// stopTrackingKeyboard()
+// }
+// }
+//
+// @objc private func updateKeyboardFrame() {
+// print("updateKeyboardFrame")
+// channel!.invokeMethod("updatKeyboardFrame", arguments: nil)
+// guard let window = window else { return }
+//
+// let screenHeight = window.bounds.height
+// let keyboardHeight = max(0, screenHeight - keyboardFrame.origin.y)
+//
+// channel!.invokeMethod("keyboardGeometry", arguments: [
+// "y": keyboardFrame.origin.y,
+// "height": keyboardHeight
+// ])
+//
+// // Stop tracking when the animation completes
+// if keyboardHeight == 0 || keyboardHeight == keyboardFrame.height {
+// stopTrackingKeyboard()
+// }
+// }
+//
+// private func stopTrackingKeyboard() {
+// print("stopTrackingKeyboard(), timer: ", keyboardTimer)
+// isAnimating = false
+// keyboardTimer?.invalidate()
+// keyboardTimer = nil
+// }
+
+ @objc private func keyboardWillHide(_ notification: Notification) {
+ channel!.invokeMethod("keyboardWillHide", arguments: nil)
+ }
+
+ @objc private func keyboardDidHide(_ notification: Notification) {
+ channel!.invokeMethod("keyboardDidHide", arguments: nil)
+// stopTrackingKeyboard()
+ }
+}
+
+enum KeyboardType {
+ case unknown
+ case full
+ case minimized
+
+ var description: String {
+ switch self {
+ case .unknown:
+ "unknown"
+ case .full:
+ "full"
+ case .minimized:
+ "minimized"
+ }
+ }
+}
diff --git a/super_keyboard/ios/Resources/PrivacyInfo.xcprivacy b/super_keyboard/ios/Resources/PrivacyInfo.xcprivacy
new file mode 100644
index 0000000000..a34b7e2e60
--- /dev/null
+++ b/super_keyboard/ios/Resources/PrivacyInfo.xcprivacy
@@ -0,0 +1,14 @@
+
+
+
+
+ NSPrivacyTrackingDomains
+
+ NSPrivacyAccessedAPITypes
+
+ NSPrivacyCollectedDataTypes
+
+ NSPrivacyTracking
+
+
+
diff --git a/super_keyboard/ios/super_keyboard.podspec b/super_keyboard/ios/super_keyboard.podspec
new file mode 100644
index 0000000000..aad3acf70e
--- /dev/null
+++ b/super_keyboard/ios/super_keyboard.podspec
@@ -0,0 +1,29 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
+# Run `pod lib lint super_keyboard.podspec` to validate before publishing.
+#
+Pod::Spec.new do |s|
+ s.name = 'super_keyboard'
+ s.version = '0.0.1'
+ s.summary = 'A plugin that reports keyboard visibility and size.'
+ s.description = <<-DESC
+A plugin that reports keyboard visibility and size.
+ DESC
+ s.homepage = 'http://example.com'
+ s.license = { :file => '../LICENSE' }
+ s.author = { 'Your Company' => 'email@example.com' }
+ s.source = { :path => '.' }
+ s.source_files = 'Classes/**/*'
+ s.dependency 'Flutter'
+ s.platform = :ios, '12.0'
+
+ # Flutter.framework does not contain a i386 slice.
+ s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
+ s.swift_version = '5.0'
+
+ # If your plugin requires a privacy manifest, for example if it uses any
+ # required reason APIs, update the PrivacyInfo.xcprivacy file to describe your
+ # plugin's privacy impact, and then uncomment this line. For more information,
+ # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
+ # s.resource_bundles = {'super_keyboard_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
+end
diff --git a/super_keyboard/lib/src/keyboard.dart b/super_keyboard/lib/src/keyboard.dart
new file mode 100644
index 0000000000..ad916f1a35
--- /dev/null
+++ b/super_keyboard/lib/src/keyboard.dart
@@ -0,0 +1,6 @@
+enum KeyboardState {
+ closed,
+ opening,
+ open,
+ closing;
+}
diff --git a/super_keyboard/lib/src/super_keyboard_android.dart b/super_keyboard/lib/src/super_keyboard_android.dart
new file mode 100644
index 0000000000..213a5312db
--- /dev/null
+++ b/super_keyboard/lib/src/super_keyboard_android.dart
@@ -0,0 +1,168 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:logging/logging.dart';
+import 'package:super_keyboard/src/keyboard.dart';
+
+class SuperKeyboardAndroidBuilder extends StatefulWidget {
+ const SuperKeyboardAndroidBuilder({
+ super.key,
+ required this.builder,
+ });
+
+ final Widget Function(BuildContext, KeyboardState) builder;
+
+ @override
+ State createState() => _SuperKeyboardAndroidBuilderState();
+}
+
+class _SuperKeyboardAndroidBuilderState extends State
+ implements SuperKeyboardAndroidListener {
+ @override
+ void initState() {
+ super.initState();
+ SuperKeyboardAndroid.instance.addListener(this);
+ }
+
+ @override
+ void dispose() {
+ SuperKeyboardAndroid.instance.removeListener(this);
+ super.dispose();
+ }
+
+ @override
+ void onKeyboardOpen() {
+ setState(() {});
+ }
+
+ @override
+ void onKeyboardOpening() {
+ setState(() {});
+ }
+
+ @override
+ void onKeyboardClosing() {
+ setState(() {});
+ }
+
+ @override
+ void onKeyboardClosed() {
+ setState(() {});
+ }
+
+ @override
+ void onKeyboardHeightChange(double newHeight) {
+ // no-op
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return widget.builder(
+ context,
+ SuperKeyboardAndroid.instance.keyboardState.value,
+ );
+ }
+}
+
+class SuperKeyboardAndroid {
+ static SuperKeyboardAndroid? _instance;
+ static SuperKeyboardAndroid get instance {
+ _instance ??= SuperKeyboardAndroid._();
+ return _instance!;
+ }
+
+ static final log = Logger("super_keyboard.android");
+
+ SuperKeyboardAndroid._() {
+ log.info("Initializing Android plugin for super_keyboard");
+ assert(
+ defaultTargetPlatform == TargetPlatform.android,
+ "You shouldn't initialize SuperKeyboardAndroid when not on an Android platform. Current: $defaultTargetPlatform",
+ );
+ _methodChannel.setMethodCallHandler(_onPlatformMessage);
+ }
+
+ final _methodChannel = const MethodChannel('super_keyboard_android');
+
+ ValueListenable get keyboardState => _keyboardState;
+ final _keyboardState = ValueNotifier(KeyboardState.closed);
+
+ ValueListenable get keyboardHeight => _keyboardHeight;
+ final _keyboardHeight = ValueNotifier(0.0);
+
+ final _listeners = {};
+ void addListener(SuperKeyboardAndroidListener listener) => _listeners.add(listener);
+ void removeListener(SuperKeyboardAndroidListener listener) => _listeners.remove(listener);
+
+ Future _onPlatformMessage(MethodCall message) async {
+ log.fine("Android platform message: '${message.method}', args: ${message.arguments}");
+ switch (message.method) {
+ case "keyboardOpening":
+ if (_keyboardState.value == KeyboardState.opening) {
+ return;
+ }
+
+ _keyboardState.value = KeyboardState.opening;
+
+ for (final listener in _listeners) {
+ listener.onKeyboardOpening();
+ }
+ break;
+ case "keyboardOpened":
+ if (_keyboardState.value == KeyboardState.open) {
+ return;
+ }
+
+ _keyboardState.value = KeyboardState.open;
+
+ for (final listener in _listeners) {
+ listener.onKeyboardOpen();
+ }
+ break;
+ case "keyboardClosing":
+ if (_keyboardState.value == KeyboardState.closing) {
+ return;
+ }
+
+ _keyboardState.value = KeyboardState.closing;
+
+ for (final listener in _listeners) {
+ listener.onKeyboardClosing();
+ }
+ break;
+ case "keyboardClosed":
+ if (_keyboardState.value == KeyboardState.closed) {
+ return;
+ }
+
+ _keyboardState.value = KeyboardState.closed;
+
+ for (final listener in _listeners) {
+ listener.onKeyboardClosed();
+ }
+ break;
+ case "onProgress":
+ final keyboardHeight = message.arguments["keyboardHeight"] as num?;
+ if (keyboardHeight == null) {
+ break;
+ }
+
+ _keyboardHeight.value = keyboardHeight.toDouble();
+
+ for (final listener in _listeners) {
+ listener.onKeyboardHeightChange(keyboardHeight.toDouble());
+ }
+ break;
+ default:
+ log.warning("Unknown Android plugin platform message: $message");
+ }
+ }
+}
+
+abstract interface class SuperKeyboardAndroidListener {
+ void onKeyboardOpening();
+ void onKeyboardOpen();
+ void onKeyboardClosing();
+ void onKeyboardClosed();
+ void onKeyboardHeightChange(double height);
+}
diff --git a/super_keyboard/lib/src/super_keyboard_ios.dart b/super_keyboard/lib/src/super_keyboard_ios.dart
new file mode 100644
index 0000000000..7b82e28221
--- /dev/null
+++ b/super_keyboard/lib/src/super_keyboard_ios.dart
@@ -0,0 +1,139 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:logging/logging.dart';
+import 'package:super_keyboard/src/keyboard.dart';
+
+class SuperKeyboardIOSBuilder extends StatefulWidget {
+ const SuperKeyboardIOSBuilder({
+ super.key,
+ required this.builder,
+ });
+
+ final Widget Function(BuildContext, KeyboardState) builder;
+
+ @override
+ State createState() => _SuperKeyboardIOSBuilderState();
+}
+
+class _SuperKeyboardIOSBuilderState extends State implements SuperKeyboardIOSListener {
+ @override
+ void initState() {
+ super.initState();
+ SuperKeyboardIOS.instance.addListener(this);
+ }
+
+ @override
+ void dispose() {
+ SuperKeyboardIOS.instance.removeListener(this);
+ super.dispose();
+ }
+
+ @override
+ void onKeyboardWillShow() {
+ setState(() {});
+ }
+
+ @override
+ void onKeyboardDidShow() {
+ setState(() {});
+ }
+
+ @override
+ void onKeyboardWillHide() {
+ setState(() {});
+ }
+
+ @override
+ void onKeyboardDidHide() {
+ setState(() {});
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return widget.builder(
+ context,
+ SuperKeyboardIOS.instance.keyboardState.value,
+ );
+ }
+}
+
+class SuperKeyboardIOS {
+ static SuperKeyboardIOS? _instance;
+ static SuperKeyboardIOS get instance {
+ _instance ??= SuperKeyboardIOS._();
+ return _instance!;
+ }
+
+ static final log = Logger("super_keyboard.ios");
+
+ SuperKeyboardIOS._() {
+ log.info("Initializing iOS plugin for super_keyboard");
+ assert(
+ defaultTargetPlatform == TargetPlatform.iOS,
+ "You shouldn't initialize SuperKeyboardIOS when not on an iOS platform. Current: $defaultTargetPlatform",
+ );
+ _methodChannel.setMethodCallHandler(_onPlatformMessage);
+ }
+
+ final _methodChannel = const MethodChannel('super_keyboard_ios');
+
+ ValueListenable get keyboardState => _keyboardState;
+ final _keyboardState = ValueNotifier(KeyboardState.closed);
+
+ final _listeners = {};
+ void addListener(SuperKeyboardIOSListener listener) => _listeners.add(listener);
+ void removeListener(SuperKeyboardIOSListener listener) => _listeners.remove(listener);
+
+ Future _onPlatformMessage(MethodCall message) async {
+ assert(() {
+ log.fine("iOS platform message: '${message.method}', args: ${message.arguments}");
+ return true;
+ }());
+
+ switch (message.method) {
+ case "keyboardWillShow":
+ log.info("keyboardWillShow");
+ _keyboardState.value = KeyboardState.opening;
+
+ for (final listener in _listeners) {
+ listener.onKeyboardWillShow();
+ }
+ break;
+ case "keyboardDidShow":
+ log.info("keyboardDidShow");
+ _keyboardState.value = KeyboardState.open;
+
+ for (final listener in _listeners) {
+ listener.onKeyboardDidShow();
+ }
+ break;
+ case "keyboardWillChangeFrame":
+ log.info("keyboardWillChangeFrame - keyboard type: ${message.arguments['keyboardType']}");
+ break;
+ case "keyboardWillHide":
+ log.info("keyboardWillHide");
+ _keyboardState.value = KeyboardState.closing;
+
+ for (final listener in _listeners) {
+ listener.onKeyboardWillHide();
+ }
+ break;
+ case "keyboardDidHide":
+ log.info("keyboardDidHide");
+ _keyboardState.value = KeyboardState.closed;
+
+ for (final listener in _listeners) {
+ listener.onKeyboardDidHide();
+ }
+ break;
+ }
+ }
+}
+
+abstract interface class SuperKeyboardIOSListener {
+ void onKeyboardWillShow();
+ void onKeyboardDidShow();
+ void onKeyboardWillHide();
+ void onKeyboardDidHide();
+}
diff --git a/super_keyboard/lib/src/super_keyboard_unified.dart b/super_keyboard/lib/src/super_keyboard_unified.dart
new file mode 100644
index 0000000000..044c07b1e6
--- /dev/null
+++ b/super_keyboard/lib/src/super_keyboard_unified.dart
@@ -0,0 +1,129 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+import 'package:logging/logging.dart';
+import 'package:super_keyboard/src/keyboard.dart';
+import 'package:super_keyboard/src/super_keyboard_android.dart';
+import 'package:super_keyboard/src/super_keyboard_ios.dart';
+
+class SuperKeyboardBuilder extends StatefulWidget {
+ const SuperKeyboardBuilder({
+ super.key,
+ required this.builder,
+ });
+
+ final Widget Function(BuildContext, KeyboardState) builder;
+
+ @override
+ State createState() => _SuperKeyboardBuilderState();
+}
+
+class _SuperKeyboardBuilderState extends State {
+ @override
+ void initState() {
+ super.initState();
+ SuperKeyboard.instance.state.addListener(_onKeyboardStateChange);
+ }
+
+ @override
+ void dispose() {
+ SuperKeyboard.instance.state.removeListener(_onKeyboardStateChange);
+ super.dispose();
+ }
+
+ void _onKeyboardStateChange() {
+ setState(() {
+ // Re-build.
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return widget.builder(
+ context,
+ SuperKeyboard.instance.state.value,
+ );
+ }
+}
+
+/// A unified API for tracking the software keyboard status, regardless
+/// of platform.
+class SuperKeyboard {
+ static SuperKeyboard? _instance;
+ static SuperKeyboard get instance {
+ _instance ??= SuperKeyboard._();
+ return _instance!;
+ }
+
+ @visibleForTesting
+ static set testInstance(SuperKeyboard? testInstance) => _instance = testInstance;
+
+ static final log = Logger("super_keyboard");
+
+ static void initLogs([Level level = Level.ALL]) {
+ hierarchicalLoggingEnabled = true;
+ log.level = level;
+ log.onRecord.listen((record) {
+ // ignore: avoid_print
+ print('${record.level.name}: ${record.time.toLogTime()}: ${record.message}');
+ });
+ }
+
+ SuperKeyboard._() {
+ _init();
+ }
+
+ void _init() {
+ log.info("Initializing SuperKeyboard");
+ if (defaultTargetPlatform == TargetPlatform.iOS) {
+ log.fine("SuperKeyboard - Initializing for iOS");
+ SuperKeyboardIOS.instance.keyboardState.addListener(_onIOSKeyboardChange);
+ } else if (defaultTargetPlatform == TargetPlatform.android) {
+ log.fine("SuperKeyboard - Initializing for Android");
+ SuperKeyboardAndroid.instance.keyboardState.addListener(_onAndroidKeyboardChange);
+ SuperKeyboardAndroid.instance.keyboardHeight.addListener(_onAndroidKeyboardHeightChange);
+ }
+ }
+
+ ValueListenable get state => _state;
+ final _state = ValueNotifier(KeyboardState.closed);
+
+ ValueListenable get keyboardHeight => _keyboardHeight;
+ final _keyboardHeight = ValueNotifier(null);
+
+ void _onIOSKeyboardChange() {
+ _state.value = SuperKeyboardIOS.instance.keyboardState.value;
+ }
+
+ void _onAndroidKeyboardChange() {
+ _state.value = SuperKeyboardAndroid.instance.keyboardState.value;
+ }
+
+ void _onAndroidKeyboardHeightChange() {
+ _keyboardHeight.value = SuperKeyboardAndroid.instance.keyboardHeight.value;
+ }
+}
+
+extension on DateTime {
+ String toLogTime() {
+ String h = _twoDigits(hour);
+ String min = _twoDigits(minute);
+ String sec = _twoDigits(second);
+ String ms = _threeDigits(millisecond);
+ if (isUtc) {
+ return "$h:$min:$sec.$ms";
+ } else {
+ return "$h:$min:$sec.$ms";
+ }
+ }
+
+ String _threeDigits(int n) {
+ if (n >= 100) return "$n";
+ if (n >= 10) return "0$n";
+ return "00$n";
+ }
+
+ String _twoDigits(int n) {
+ if (n >= 10) return "$n";
+ return "0$n";
+ }
+}
diff --git a/super_keyboard/lib/super_keyboard.dart b/super_keyboard/lib/super_keyboard.dart
new file mode 100644
index 0000000000..20b32f156a
--- /dev/null
+++ b/super_keyboard/lib/super_keyboard.dart
@@ -0,0 +1,4 @@
+export 'src/super_keyboard_unified.dart';
+export 'src/super_keyboard_ios.dart';
+export 'src/super_keyboard_android.dart';
+export 'src/keyboard.dart';
diff --git a/super_keyboard/lib/super_keyboard_test.dart b/super_keyboard/lib/super_keyboard_test.dart
new file mode 100644
index 0000000000..a8ce576b45
--- /dev/null
+++ b/super_keyboard/lib/super_keyboard_test.dart
@@ -0,0 +1 @@
+export 'test/keyboard_simulator.dart';
diff --git a/super_keyboard/lib/test/keyboard_simulator.dart b/super_keyboard/lib/test/keyboard_simulator.dart
new file mode 100644
index 0000000000..3b93712331
--- /dev/null
+++ b/super_keyboard/lib/test/keyboard_simulator.dart
@@ -0,0 +1,276 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_test_runners/flutter_test_runners.dart';
+import 'package:super_keyboard/src/keyboard.dart';
+import 'package:super_keyboard/src/super_keyboard_unified.dart';
+
+/// A widget that simulates the software keyboard appearance and disappearance.
+///
+/// This works by listening to messages sent from Flutter to the platform that show/hide
+/// the software keyboard. In response to those messages, [SuperKeyboard] emits
+/// notifications for the keyboard opening, opened, closing, closed. The timing of those
+/// messages are based on an animation in this widget, simulating actual keyboard expansion
+/// and collapse. Similarly, this widget installs a `MediaQuery`, which sets its bottom
+/// offsets equal to the simulated keyboard height, which reflects how Flutter actually
+/// reports keyboard height to Flutter apps.
+///
+/// Place this widget above the `Scaffold` in the widget tree.
+class SoftwareKeyboardHeightSimulator extends StatefulWidget {
+ const SoftwareKeyboardHeightSimulator({
+ super.key,
+ required this.tester,
+ this.isEnabled = true,
+ this.enableForAllPlatforms = false,
+ this.keyboardHeight = _defaultKeyboardHeight,
+ this.animateKeyboard = false,
+ required this.child,
+ });
+
+ /// Flutter's [WidgetTester], which is used to intercept platform messages
+ /// about opening/closing the keyboard.
+ final WidgetTester tester;
+
+ /// Whether or not to enable the simulated software keyboard insets.
+ ///
+ /// This property is provided so that clients don't need to conditionally add/remove
+ /// this widget from the tree. Instead this flag can be flipped, as needed.
+ final bool isEnabled;
+
+ /// Whether to simulate software keyboard insets for all platforms (`true`), or whether to
+ /// only simulate software keyboard insets for mobile platforms, e.g., Android, iOS (`false`).
+ ///
+ /// The value for this property should remain constant within a single test. Don't
+ /// attempt to enable and then disable keyboard simulation. That behavior is undefined.
+ final bool enableForAllPlatforms;
+
+ /// The vertical space, in logical pixels, to occupy at the bottom of the screen to simulate the appearance
+ /// of a keyboard.
+ final double keyboardHeight;
+
+ /// Whether to simulate keyboard open/closing animations.
+ ///
+ /// These animations change the keyboard insets over time, similar to how a real
+ /// software keyboard slides up/down. However, this also means that clients need to
+ /// `pumpAndSettle()` to ensure the animation is complete. If you want to avoid `pumpAndSettle()`
+ /// and you don't care about the animation, then pass `false` to disable the animations.
+ final bool animateKeyboard;
+
+ final Widget child;
+
+ @override
+ State createState() => _SoftwareKeyboardHeightSimulatorState();
+}
+
+class _SoftwareKeyboardHeightSimulatorState extends State
+ with SingleTickerProviderStateMixin {
+ @override
+ void initState() {
+ super.initState();
+
+ if (widget.isEnabled) {
+ TestSuperKeyboard.install(
+ widget.tester,
+ fakeKeyboardHeight: widget.keyboardHeight,
+ keyboardAnimationTime: widget.animateKeyboard ? const Duration(milliseconds: 600) : Duration.zero,
+ );
+ }
+ }
+
+ @override
+ void didUpdateWidget(covariant SoftwareKeyboardHeightSimulator oldWidget) {
+ super.didUpdateWidget(oldWidget);
+
+ if (widget.animateKeyboard != oldWidget.animateKeyboard || widget.keyboardHeight != oldWidget.keyboardHeight) {
+ TestSuperKeyboard.uninstall();
+
+ TestSuperKeyboard.install(
+ widget.tester,
+ fakeKeyboardHeight: widget.keyboardHeight,
+ keyboardAnimationTime: widget.animateKeyboard ? const Duration(milliseconds: 600) : Duration.zero,
+ );
+ }
+
+ if (widget.isEnabled && !oldWidget.isEnabled) {
+ throw Exception(
+ "You initially built a SoftwareKeyboardHeightSimulator disabled, then you enabled it. This mode needs to remain constant throughout a test.");
+ } else if (!widget.isEnabled && oldWidget.isEnabled) {
+ throw Exception(
+ "You initially built a SoftwareKeyboardHeightSimulator enabled, then you disabled it. This mode needs to remain constant throughout a test.");
+ }
+ }
+
+ @override
+ void dispose() {
+ TestSuperKeyboard.uninstall();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return ValueListenableBuilder(
+ valueListenable: SuperKeyboard.instance.keyboardHeight,
+ builder: (context, keyboardHeight, child) {
+ final realMediaQuery = MediaQuery.of(context);
+ final isRelevantPlatform = widget.enableForAllPlatforms ||
+ (defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS);
+ final shouldSimulate = widget.isEnabled && isRelevantPlatform;
+ if (!shouldSimulate) {
+ return widget.child;
+ }
+
+ return Directionality(
+ // For some reason a Stack needs Directionality.
+ textDirection: TextDirection.ltr,
+ child: Stack(
+ children: [
+ Positioned.fill(
+ child: MediaQuery(
+ data: realMediaQuery.copyWith(
+ viewInsets: realMediaQuery.viewInsets.copyWith(
+ bottom: keyboardHeight ?? 0.0,
+ ),
+ ),
+ child: widget.child,
+ ),
+ ),
+ // Display a placeholder where the keyboard would go so we
+ // can verify the keyboard size in golden tests.
+ Positioned(
+ left: 0,
+ right: 0,
+ bottom: 0,
+ height: keyboardHeight,
+ child: const Placeholder(),
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+}
+
+class TestSuperKeyboard implements SuperKeyboard {
+ static void install(
+ WidgetTester tester, {
+ double fakeKeyboardHeight = _defaultKeyboardHeight,
+ Duration keyboardAnimationTime = const Duration(milliseconds: 600),
+ }) {
+ if (_instance != null) {
+ return;
+ }
+
+ _instance = TestSuperKeyboard(
+ tester,
+ fakeKeyboardHeight: fakeKeyboardHeight,
+ keyboardAnimationTime: keyboardAnimationTime,
+ );
+
+ SuperKeyboard.testInstance = _instance;
+ }
+
+ static void uninstall() {
+ if (_instance == null) {
+ return;
+ }
+
+ _instance!.dispose();
+ _instance = null;
+
+ SuperKeyboard.testInstance = null;
+ }
+
+ static TestSuperKeyboard? _instance;
+
+ TestSuperKeyboard(
+ this.tester, {
+ this.fakeKeyboardHeight = 400.0,
+ Duration keyboardAnimationTime = const Duration(milliseconds: 600),
+ }) {
+ _interceptPlatformChannel();
+
+ _keyboardHeightController = AnimationController(
+ duration: keyboardAnimationTime,
+ vsync: tester,
+ )
+ ..addListener(() {
+ _keyboardHeight.value = _keyboardHeightController.value * fakeKeyboardHeight;
+ })
+ ..addStatusListener(_onKeyboardAnimationStatusChange);
+ }
+
+ void _interceptPlatformChannel() {
+ tester.interceptChannel(SystemChannels.textInput.name) //
+ ..interceptMethod(
+ 'TextInput.setClient',
+ (methodCall) {
+ _simulatePlatformOpeningKeyboard();
+ return null;
+ },
+ )
+ ..interceptMethod(
+ 'TextInput.show',
+ (methodCall) {
+ _simulatePlatformOpeningKeyboard();
+ return null;
+ },
+ )
+ ..interceptMethod(
+ 'TextInput.hide',
+ (methodCall) {
+ _simulatePlatformClosingKeyboard();
+ return null;
+ },
+ )
+ ..interceptMethod(
+ 'TextInput.clearClient',
+ (methodCall) {
+ _simulatePlatformClosingKeyboard();
+ return null;
+ },
+ );
+ }
+
+ void dispose() {
+ tester.binding.defaultBinaryMessenger.setMockMessageHandler(SystemChannels.textInput.name, null);
+ _keyboardHeightController.dispose();
+ }
+
+ final WidgetTester tester;
+ final double fakeKeyboardHeight;
+
+ late final AnimationController _keyboardHeightController;
+
+ @override
+ ValueListenable get keyboardHeight => _keyboardHeight;
+ final _keyboardHeight = ValueNotifier(0.0);
+
+ @override
+ ValueListenable get state => _state;
+ final _state = ValueNotifier(KeyboardState.closed);
+
+ void _simulatePlatformOpeningKeyboard() {
+ _keyboardHeightController.forward();
+ }
+
+ void _simulatePlatformClosingKeyboard() {
+ _keyboardHeightController.reverse();
+ }
+
+ void _onKeyboardAnimationStatusChange(AnimationStatus status) {
+ switch (status) {
+ case AnimationStatus.forward:
+ _state.value = KeyboardState.opening;
+ case AnimationStatus.completed:
+ _state.value = KeyboardState.open;
+ case AnimationStatus.reverse:
+ _state.value = KeyboardState.closing;
+ case AnimationStatus.dismissed:
+ _state.value = KeyboardState.closed;
+ }
+ }
+}
+
+const _defaultKeyboardHeight = 300.0;
diff --git a/super_keyboard/pubspec.yaml b/super_keyboard/pubspec.yaml
new file mode 100644
index 0000000000..3df862ca39
--- /dev/null
+++ b/super_keyboard/pubspec.yaml
@@ -0,0 +1,81 @@
+name: super_keyboard
+description: "A plugin that reports keyboard visibility and size."
+version: 0.1.0+1
+homepage: https://github.com/superlistapp/super_editor
+funding:
+ - https://flutterbountyhunters.com
+ - https://github.com/sponsors/matthew-carroll
+topics:
+ - software-keyboard
+ - keyboard
+ - rich-text-editor
+ - editor
+
+environment:
+ sdk: ^3.5.0
+ flutter: '>=3.3.0'
+
+dependencies:
+ flutter:
+ sdk: flutter
+
+ plugin_platform_interface: ^2.0.2
+ logging: ^1.3.0
+
+ # So that we can expose test tools to apps.
+ flutter_test:
+ sdk: flutter
+ flutter_test_runners: ^0.0.4
+
+dev_dependencies:
+ flutter_lints: ^4.0.0
+
+flutter:
+ # This section identifies this Flutter project as a plugin project.
+ # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
+ # which should be registered in the plugin registry. This is required for
+ # using method channels.
+ # The Android 'package' specifies package in which the registered class is.
+ # This is required for using method channels on Android.
+ # The 'ffiPlugin' specifies that native code should be built and bundled.
+ # This is required for using `dart:ffi`.
+ # All these are used by the tooling to maintain consistency when
+ # adding or updating assets for this project.
+ plugin:
+ platforms:
+ android:
+ package: com.flutterbountyhunters.superkeyboard.super_keyboard
+ pluginClass: SuperKeyboardPlugin
+ ios:
+ pluginClass: SuperKeyboardPlugin
+
+ # To add assets to your plugin package, add an assets section, like this:
+ # assets:
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+ #
+ # For details regarding assets in packages, see
+ # https://flutter.dev/to/asset-from-package
+ #
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/to/resolution-aware-images
+
+ # To add custom fonts to your plugin package, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts in packages, see
+ # https://flutter.dev/to/font-from-package
diff --git a/super_keyboard/test/keyboard_simulation_test.dart b/super_keyboard/test/keyboard_simulation_test.dart
new file mode 100644
index 0000000000..ea9d4da273
--- /dev/null
+++ b/super_keyboard/test/keyboard_simulation_test.dart
@@ -0,0 +1,156 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_test_runners/flutter_test_runners.dart';
+import 'package:super_keyboard/super_keyboard.dart';
+import 'package:super_keyboard/super_keyboard_test.dart';
+
+void main() {
+ group("Super Keyboard Test Tools > keyboard simulation >", () {
+ testWidgets("opens and closes", (tester) async {
+ final screenKey = GlobalKey();
+ final contentKey = GlobalKey();
+ await _pumpScaffold(
+ tester,
+ screenKey: screenKey,
+ contentKey: contentKey,
+ );
+
+ // Ensure the keyboard is closed, initially.
+ expect(SuperKeyboard.instance.state.value, KeyboardState.closed);
+ expect(_calculateKeyboardHeight(screenKey, contentKey), 0.0);
+
+ // Focus the text field to open the keyboard.
+ await tester.tap(find.byType(TextField));
+
+ // Pump a couple frames and ensure the keyboard is opening.
+ // Note: If we don't explicitly pass a duration, the animation doesn't
+ // move forward. I don't know why.
+ await tester.pump(const Duration(milliseconds: 16));
+ await tester.pump(const Duration(milliseconds: 16));
+ expect(SuperKeyboard.instance.state.value, KeyboardState.opening);
+ expect(_calculateKeyboardHeight(screenKey, contentKey), lessThan(_keyboardHeight));
+ expect(_calculateKeyboardHeight(screenKey, contentKey), greaterThan(0));
+
+ // Let the keyboard finish opening.
+ await tester.pumpAndSettle();
+
+ // Ensure that the keyboard is fully open.
+ expect(SuperKeyboard.instance.state.value, KeyboardState.open);
+ expect(_calculateKeyboardHeight(screenKey, contentKey), _keyboardHeight);
+
+ // Tap outside the text field to unfocus it.
+ await tester.tapAt(const Offset(200, 100));
+
+ // Pump a couple frames and ensure the keyboard is closing.
+ // Note: If we don't explicitly pass a duration, the animation doesn't
+ // move forward. I don't know why.
+ await tester.pump(const Duration(milliseconds: 16));
+ await tester.pump(const Duration(milliseconds: 16));
+ expect(SuperKeyboard.instance.state.value, KeyboardState.closing);
+ expect(_calculateKeyboardHeight(screenKey, contentKey), lessThan(_keyboardHeight));
+ expect(_calculateKeyboardHeight(screenKey, contentKey), greaterThan(0));
+
+ // Let the keyboard finish closing.
+ await tester.pumpAndSettle();
+
+ // Ensure that the keyboard is fully closed.
+ expect(SuperKeyboard.instance.state.value, KeyboardState.closed);
+ expect(_calculateKeyboardHeight(screenKey, contentKey), 0.0);
+ });
+
+ testWidgetsOnMobile("enabled by default on mobile", (tester) async {
+ final screenKey = GlobalKey();
+ final contentKey = GlobalKey();
+ await _pumpScaffold(
+ tester,
+ screenKey: screenKey,
+ contentKey: contentKey,
+ );
+
+ // Ensure the keyboard is closed, initially.
+ expect(_calculateKeyboardHeight(screenKey, contentKey), 0.0);
+
+ // Focus the text field to open the keyboard.
+ await tester.tap(find.byType(TextField));
+
+ // Let the keyboard animate up.
+ await tester.pumpAndSettle();
+
+ // Ensure the keyboard is open.
+ expect(_calculateKeyboardHeight(screenKey, contentKey), _keyboardHeight);
+ });
+
+ testWidgetsOnDesktop("disabled by default on desktop", (tester) async {
+ final screenKey = GlobalKey();
+ final contentKey = GlobalKey();
+ await _pumpScaffold(
+ tester,
+ screenKey: screenKey,
+ contentKey: contentKey,
+ );
+
+ // Ensure the keyboard is closed, initially.
+ expect(_calculateKeyboardHeight(screenKey, contentKey), 0.0);
+
+ // Focus the text field to open the keyboard.
+ await tester.tap(find.byType(TextField));
+
+ // Give the keyboard a chance to animate up (it shouldn't).
+ await tester.pumpAndSettle();
+
+ // Ensure the keyboard is still closed.
+ expect(_calculateKeyboardHeight(screenKey, contentKey), 0.0);
+ });
+ });
+}
+
+Future _pumpScaffold(
+ WidgetTester tester, {
+ GlobalKey? screenKey,
+ GlobalKey? contentKey,
+}) async {
+ switch (defaultTargetPlatform) {
+ case TargetPlatform.iOS:
+ case TargetPlatform.android:
+ tester.view.physicalSize = const Size(1170, 2532); // iPhone 13 Pro
+ case TargetPlatform.macOS:
+ case TargetPlatform.windows:
+ case TargetPlatform.linux:
+ case TargetPlatform.fuchsia:
+ // Use default test window size for desktop.
+ }
+
+ await tester.pumpWidget(
+ SizedBox(
+ key: screenKey,
+ child: SoftwareKeyboardHeightSimulator(
+ tester: tester,
+ keyboardHeight: _keyboardHeight,
+ animateKeyboard: true,
+ child: MaterialApp(
+ home: Scaffold(
+ body: Center(
+ key: contentKey,
+ child: TextField(
+ onTapOutside: (event) {
+ // Remove focus on tap outside.
+ FocusManager.instance.primaryFocus?.unfocus();
+ },
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+}
+
+double _calculateKeyboardHeight(GlobalKey screenKey, GlobalKey contentKey) {
+ final screenBox = screenKey.currentContext!.findRenderObject() as RenderBox;
+ final contentBox = contentKey.currentContext!.findRenderObject() as RenderBox;
+
+ return screenBox.size.height - contentBox.size.height;
+}
+
+const _keyboardHeight = 300.0;