From 4a343e4927f4575f8530695415edcafe8d18efbe Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 15 Sep 2024 17:50:58 +0300 Subject: [PATCH 01/90] feat(quill_native_bridge): add initial implementation for getClipboardHTML() method channel for Android and iOS, example and Android platform directory --- quill_native_bridge/.metadata | 13 +- quill_native_bridge/android/.gitignore | 9 + quill_native_bridge/android/build.gradle | 51 ++ quill_native_bridge/android/settings.gradle | 1 + .../android/src/main/AndroidManifest.xml | 3 + .../QuillNativeBridgePlugin.kt | 69 ++ quill_native_bridge/example/.gitignore | 43 ++ quill_native_bridge/example/README.md | 3 + .../example/analysis_options.yaml | 31 + .../example/android/.gitignore | 13 + .../example/android/app/build.gradle | 44 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 45 ++ .../MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../example/android/build.gradle | 18 + .../example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + .../example/android/settings.gradle | 25 + quill_native_bridge/example/ios/.gitignore | 34 + .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../example/ios/Flutter/Debug.xcconfig | 2 + .../example/ios/Flutter/Release.xcconfig | 2 + quill_native_bridge/example/ios/Podfile | 44 ++ quill_native_bridge/example/ios/Podfile.lock | 28 + .../ios/Runner.xcodeproj/project.pbxproj | 728 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 98 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 +++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../example/ios/Runner/Info.plist | 49 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../example/ios/RunnerTests/RunnerTests.swift | 27 + quill_native_bridge/example/lib/main.dart | 103 +++ quill_native_bridge/example/pubspec.yaml | 24 + .../ios/Classes/QuillNativeBridgePlugin.swift | 8 + .../ios/Resources/PrivacyInfo.xcprivacy | 14 + .../lib/quill_native_bridge.dart | 15 + .../quill_native_bridge_method_channel.dart | 23 +- ...uill_native_bridge_platform_interface.dart | 9 +- quill_native_bridge/pubspec.yaml | 3 + .../test/quill_native_bridge_test.dart | 18 +- 78 files changed, 1974 insertions(+), 12 deletions(-) create mode 100644 quill_native_bridge/android/.gitignore create mode 100644 quill_native_bridge/android/build.gradle create mode 100644 quill_native_bridge/android/settings.gradle create mode 100644 quill_native_bridge/android/src/main/AndroidManifest.xml create mode 100644 quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt create mode 100644 quill_native_bridge/example/.gitignore create mode 100644 quill_native_bridge/example/README.md create mode 100644 quill_native_bridge/example/analysis_options.yaml create mode 100644 quill_native_bridge/example/android/.gitignore create mode 100644 quill_native_bridge/example/android/app/build.gradle create mode 100644 quill_native_bridge/example/android/app/src/debug/AndroidManifest.xml create mode 100644 quill_native_bridge/example/android/app/src/main/AndroidManifest.xml create mode 100644 quill_native_bridge/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt create mode 100644 quill_native_bridge/example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 quill_native_bridge/example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 quill_native_bridge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 quill_native_bridge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 quill_native_bridge/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 quill_native_bridge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 quill_native_bridge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 quill_native_bridge/example/android/app/src/main/res/values-night/styles.xml create mode 100644 quill_native_bridge/example/android/app/src/main/res/values/styles.xml create mode 100644 quill_native_bridge/example/android/app/src/profile/AndroidManifest.xml create mode 100644 quill_native_bridge/example/android/build.gradle create mode 100644 quill_native_bridge/example/android/gradle.properties create mode 100644 quill_native_bridge/example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 quill_native_bridge/example/android/settings.gradle create mode 100644 quill_native_bridge/example/ios/.gitignore create mode 100644 quill_native_bridge/example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 quill_native_bridge/example/ios/Flutter/Debug.xcconfig create mode 100644 quill_native_bridge/example/ios/Flutter/Release.xcconfig create mode 100644 quill_native_bridge/example/ios/Podfile create mode 100644 quill_native_bridge/example/ios/Podfile.lock create mode 100644 quill_native_bridge/example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 quill_native_bridge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 quill_native_bridge/example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 quill_native_bridge/example/ios/Runner/AppDelegate.swift create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 quill_native_bridge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 quill_native_bridge/example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 quill_native_bridge/example/ios/Runner/Info.plist create mode 100644 quill_native_bridge/example/ios/Runner/Runner-Bridging-Header.h create mode 100644 quill_native_bridge/example/ios/RunnerTests/RunnerTests.swift create mode 100644 quill_native_bridge/example/lib/main.dart create mode 100644 quill_native_bridge/example/pubspec.yaml create mode 100644 quill_native_bridge/ios/Resources/PrivacyInfo.xcprivacy diff --git a/quill_native_bridge/.metadata b/quill_native_bridge/.metadata index 5d0b67954..14c915b80 100644 --- a/quill_native_bridge/.metadata +++ b/quill_native_bridge/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "5874a72aa4c779a02553007c47dacbefba2374dc" + revision: "4cf269e36de2573851eaef3c763994f8f9be494d" channel: "stable" project_type: plugin @@ -13,11 +13,14 @@ project_type: plugin migration: platforms: - platform: root - create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + - platform: android + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d - platform: ios - create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d # User provided section diff --git a/quill_native_bridge/android/.gitignore b/quill_native_bridge/android/.gitignore new file mode 100644 index 000000000..161bdcdaf --- /dev/null +++ b/quill_native_bridge/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/quill_native_bridge/android/build.gradle b/quill_native_bridge/android/build.gradle new file mode 100644 index 000000000..e57ffa7e1 --- /dev/null +++ b/quill_native_bridge/android/build.gradle @@ -0,0 +1,51 @@ +group = "dev.flutterquill.quill_native_bridge" +version = "1.0-SNAPSHOT" + +buildscript { + ext.kotlin_version = "1.8.22" + repositories { + google() + mavenCentral() + } + + dependencies { + classpath("com.android.tools.build:gradle:8.1.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 = "dev.flutterquill.quill_native_bridge" + } + + 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 + } +} diff --git a/quill_native_bridge/android/settings.gradle b/quill_native_bridge/android/settings.gradle new file mode 100644 index 000000000..f757e6f01 --- /dev/null +++ b/quill_native_bridge/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'quill_native_bridge' diff --git a/quill_native_bridge/android/src/main/AndroidManifest.xml b/quill_native_bridge/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..eb0654732 --- /dev/null +++ b/quill_native_bridge/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt new file mode 100644 index 000000000..541d80405 --- /dev/null +++ b/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt @@ -0,0 +1,69 @@ +package dev.flutterquill.quill_native_bridge + +import android.content.ClipDescription +import android.content.ClipboardManager +import android.content.Context + +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { + private lateinit var channel: MethodChannel + private lateinit var context: Context + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + context = flutterPluginBinding.applicationContext + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "quill_native_bridge") + channel.setMethodCallHandler(this) + } + + override fun onMethodCall(call: MethodCall, result: Result) { + when (call.method) { + "getClipboardHTML" -> { + val clipboard = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + + if (!clipboard.hasPrimaryClip()) { + result.success(null) + return + } + + val clipData = clipboard.primaryClip + + if (clipData == null) { + result.success(null) + return + } + + val item = clipData.getItemAt(0) + + if (item.text == null || clipboard.primaryClipDescription?.hasMimeType( + ClipDescription.MIMETYPE_TEXT_HTML + ) == false + ) { + result.success(null) + return + } + + val htmlText = item.htmlText + if (htmlText == null) { + result.error( + "HTML_TEXT_NULL", + "Expected the HTML Text from Clipboard to be not null", + null + ) + return + } + result.success(htmlText) + } + + else -> result.notImplemented() + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) = + channel.setMethodCallHandler(null) +} diff --git a/quill_native_bridge/example/.gitignore b/quill_native_bridge/example/.gitignore new file mode 100644 index 000000000..29a3a5017 --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/README.md b/quill_native_bridge/example/README.md new file mode 100644 index 000000000..d659a559f --- /dev/null +++ b/quill_native_bridge/example/README.md @@ -0,0 +1,3 @@ +# đŸĒļ Quill Native Bridge Example + +Demonstrates the usage of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge) plugin. \ No newline at end of file diff --git a/quill_native_bridge/example/analysis_options.yaml b/quill_native_bridge/example/analysis_options.yaml new file mode 100644 index 000000000..cb0a7aa9c --- /dev/null +++ b/quill_native_bridge/example/analysis_options.yaml @@ -0,0 +1,31 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + always_declare_return_types: true + always_put_required_named_parameters_first: true + annotate_overrides: true + avoid_empty_else: true + avoid_escaping_inner_quotes: true + avoid_print: true + avoid_types_on_closure_parameters: true + avoid_void_async: true + cascade_invocations: true + directives_ordering: true + omit_local_variable_types: true + prefer_const_constructors: true + prefer_const_constructors_in_immutables: true + prefer_const_declarations: true + prefer_final_fields: true + prefer_final_in_for_each: true + prefer_final_locals: true + prefer_initializing_formals: true + prefer_int_literals: true + prefer_interpolation_to_compose_strings: true + prefer_relative_imports: true + prefer_single_quotes: true + sort_constructors_first: true + sort_unnamed_constructors_first: true + unnecessary_lambdas: true + unnecessary_parenthesis: true + unnecessary_string_interpolations: true diff --git a/quill_native_bridge/example/android/.gitignore b/quill_native_bridge/example/android/.gitignore new file mode 100644 index 000000000..55afd919c --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/android/app/build.gradle b/quill_native_bridge/example/android/app/build.gradle new file mode 100644 index 000000000..a8cb144d9 --- /dev/null +++ b/quill_native_bridge/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 = "dev.flutterquill.quill_native_bridge_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 = "dev.flutterquill.quill_native_bridge_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/quill_native_bridge/example/android/app/src/debug/AndroidManifest.xml b/quill_native_bridge/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/quill_native_bridge/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml b/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..4ac0b4859 --- /dev/null +++ b/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/quill_native_bridge/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt b/quill_native_bridge/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt new file mode 100644 index 000000000..a36c8719f --- /dev/null +++ b/quill_native_bridge/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt @@ -0,0 +1,5 @@ +package dev.flutterquill.quill_native_bridge_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/quill_native_bridge/example/android/app/src/main/res/drawable-v21/launch_background.xml b/quill_native_bridge/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/quill_native_bridge/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/quill_native_bridge/example/android/app/src/main/res/drawable/launch_background.xml b/quill_native_bridge/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/quill_native_bridge/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/quill_native_bridge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/quill_native_bridge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/quill_native_bridge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/quill_native_bridge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/quill_native_bridge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/android/app/src/main/res/values-night/styles.xml b/quill_native_bridge/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/quill_native_bridge/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/quill_native_bridge/example/android/app/src/main/res/values/styles.xml b/quill_native_bridge/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..cb1ef8805 --- /dev/null +++ b/quill_native_bridge/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/quill_native_bridge/example/android/app/src/profile/AndroidManifest.xml b/quill_native_bridge/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/quill_native_bridge/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/quill_native_bridge/example/android/build.gradle b/quill_native_bridge/example/android/build.gradle new file mode 100644 index 000000000..d2ffbffa4 --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/android/gradle.properties b/quill_native_bridge/example/android/gradle.properties new file mode 100644 index 000000000..259717082 --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/android/gradle/wrapper/gradle-wrapper.properties b/quill_native_bridge/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..7bb2df6ba --- /dev/null +++ b/quill_native_bridge/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/quill_native_bridge/example/android/settings.gradle b/quill_native_bridge/example/android/settings.gradle new file mode 100644 index 000000000..b9e43bd37 --- /dev/null +++ b/quill_native_bridge/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 "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/quill_native_bridge/example/ios/.gitignore b/quill_native_bridge/example/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/ios/Flutter/AppFrameworkInfo.plist b/quill_native_bridge/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..7c5696400 --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/ios/Flutter/Debug.xcconfig b/quill_native_bridge/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..ec97fc6f3 --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/ios/Flutter/Release.xcconfig b/quill_native_bridge/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..c4855bfe2 --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/ios/Podfile b/quill_native_bridge/example/ios/Podfile new file mode 100644 index 000000000..d97f17e22 --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/ios/Podfile.lock b/quill_native_bridge/example/ios/Podfile.lock new file mode 100644 index 000000000..840342f09 --- /dev/null +++ b/quill_native_bridge/example/ios/Podfile.lock @@ -0,0 +1,28 @@ +PODS: + - Flutter (1.0.0) + - integration_test (0.0.1): + - Flutter + - quill_native_bridge (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) + - quill_native_bridge (from `.symlinks/plugins/quill_native_bridge/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + integration_test: + :path: ".symlinks/plugins/integration_test/ios" + quill_native_bridge: + :path: ".symlinks/plugins/quill_native_bridge/ios" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 + quill_native_bridge: e5afa7d49c08cf68c52a5e23bc272eba6925c622 + +PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 + +COCOAPODS: 1.15.2 diff --git a/quill_native_bridge/example/ios/Runner.xcodeproj/project.pbxproj b/quill_native_bridge/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..8bf4b02fa --- /dev/null +++ b/quill_native_bridge/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,728 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 04485D02B3ADDEE8ED196D55 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9AC6E698DD4A34C635BA448 /* Pods_RunnerTests.framework */; }; + 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 */; }; + 5F41E8A1237947A9C892201F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 409E53903E588B6FEB995A19 /* Pods_Runner.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 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 */; }; +/* 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 */ + 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 = ""; }; + 409E53903E588B6FEB995A19 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 443FCBF08FBB508D6CF6608C /* 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 = ""; }; + 5D0CE3E32781BAA4A6715A5F /* 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 = ""; }; + 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 = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7EFE106A355305B732F1B40D /* 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 = ""; }; + 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 = ""; }; + B0AB33027B06A6717E4EDF35 /* 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 = ""; }; + B3D9E0EEDA4BC0EECEB6F86D /* 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 = ""; }; + E9AC6E698DD4A34C635BA448 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EB9FEAE97A5BA3ACE19C6E4B /* 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 607C6758DA859CCA786561A0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 04485D02B3ADDEE8ED196D55 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5F41E8A1237947A9C892201F /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 311C8597AB96BBAC4CA75E79 /* Pods */ = { + isa = PBXGroup; + children = ( + B0AB33027B06A6717E4EDF35 /* Pods-Runner.debug.xcconfig */, + 443FCBF08FBB508D6CF6608C /* Pods-Runner.release.xcconfig */, + 5D0CE3E32781BAA4A6715A5F /* Pods-Runner.profile.xcconfig */, + B3D9E0EEDA4BC0EECEB6F86D /* Pods-RunnerTests.debug.xcconfig */, + EB9FEAE97A5BA3ACE19C6E4B /* Pods-RunnerTests.release.xcconfig */, + 7EFE106A355305B732F1B40D /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 60298DE18DD2259B1F70E16A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 409E53903E588B6FEB995A19 /* Pods_Runner.framework */, + E9AC6E698DD4A34C635BA448 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + 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 */, + 311C8597AB96BBAC4CA75E79 /* Pods */, + 60298DE18DD2259B1F70E16A /* 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 = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 9CE04CE57D6F8D16DB16440F /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 607C6758DA859CCA786561A0 /* 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 = ( + D348EA123CF19995BC7E5EF3 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 930CBC47A47AC491416D4034 /* [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"; + }; + 930CBC47A47AC491416D4034 /* [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; + }; + 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"; + }; + 9CE04CE57D6F8D16DB16440F /* [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; + }; + D348EA123CF19995BC7E5EF3 /* [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; + }; +/* 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 = dev.flutterquill.quillNativeBridgeExample; + 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 = B3D9E0EEDA4BC0EECEB6F86D /* 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 = dev.flutterquill.quillNativeBridgeExample.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 = EB9FEAE97A5BA3ACE19C6E4B /* 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 = dev.flutterquill.quillNativeBridgeExample.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 = 7EFE106A355305B732F1B40D /* 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 = dev.flutterquill.quillNativeBridgeExample.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 = dev.flutterquill.quillNativeBridgeExample; + 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 = dev.flutterquill.quillNativeBridgeExample; + 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/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/quill_native_bridge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/quill_native_bridge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..8e3ca5dfe --- /dev/null +++ b/quill_native_bridge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/quill_native_bridge/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/quill_native_bridge/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/quill_native_bridge/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/quill_native_bridge/example/ios/Runner/AppDelegate.swift b/quill_native_bridge/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..626664468 --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/quill_native_bridge/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/quill_native_bridge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/quill_native_bridge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/quill_native_bridge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/quill_native_bridge/example/ios/Runner/Base.lproj/Main.storyboard b/quill_native_bridge/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/quill_native_bridge/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/quill_native_bridge/example/ios/Runner/Info.plist b/quill_native_bridge/example/ios/Runner/Info.plist new file mode 100644 index 000000000..629de38ee --- /dev/null +++ b/quill_native_bridge/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Quill Native Bridge + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + quill_native_bridge_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/quill_native_bridge/example/ios/Runner/Runner-Bridging-Header.h b/quill_native_bridge/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/quill_native_bridge/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/quill_native_bridge/example/ios/RunnerTests/RunnerTests.swift b/quill_native_bridge/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..890ebd3b3 --- /dev/null +++ b/quill_native_bridge/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,27 @@ +import Flutter +import UIKit +import XCTest + + +@testable import quill_native_bridge + +// 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 = QuillNativeBridgePlugin() + + 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/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/example/lib/main.dart new file mode 100644 index 000000000..645553af5 --- /dev/null +++ b/quill_native_bridge/example/lib/main.dart @@ -0,0 +1,103 @@ +import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show Clipboard, ClipboardData; +import 'package:quill_native_bridge/quill_native_bridge.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Quill Native Bridge'), + ), + body: const Center( + child: Buttons(), + ), + ), + ); + } +} + +class Buttons extends StatelessWidget { + const Buttons({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + if (kIsWeb) { + scaffoldMessenger.showText( + "Can't check if the device is simulator on web.", + ); + return; + } + if (defaultTargetPlatform != TargetPlatform.iOS) { + scaffoldMessenger.showText( + 'Must be on iOS to check if simualtor.', + ); + return; + } + final result = await QuillNativeBridge.isIOSSimulator(); + scaffoldMessenger.showText(result + ? "You're running the app on iOS simulator" + : "You're running the app on real iOS device."); + }, + child: const Text('Is iOS Simulator'), + ), + ElevatedButton( + onPressed: () async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + if (kIsWeb) { + scaffoldMessenger.showText( + "Can't get the HTML content from Clipboard on web without paste event on web.", + ); + return; + } + if (!{TargetPlatform.android, TargetPlatform.iOS} + .contains(defaultTargetPlatform)) { + scaffoldMessenger.showText( + 'Currently, this functionality is only supported on Android and iOS.', + ); + return; + } + final result = await QuillNativeBridge.getClipboardHTML(); + if (result == null) { + scaffoldMessenger.showText( + 'The HTML is not available on the clipboard.', + ); + return; + } + scaffoldMessenger.showText( + 'HTML copied to clipboard: $result', + ); + await Clipboard.setData(ClipboardData(text: result)); + debugPrint('HTML from the clipboard: $result'); + }, + child: const Text('Get HTML from Clipboard'), + ), + ], + ); + } +} + +extension ScaffoldMessengerX on ScaffoldMessengerState { + void showText(String text) { + clearSnackBars(); + showSnackBar( + SnackBar( + content: Text(text), + ), + ); + } +} diff --git a/quill_native_bridge/example/pubspec.yaml b/quill_native_bridge/example/pubspec.yaml new file mode 100644 index 000000000..bbd5db875 --- /dev/null +++ b/quill_native_bridge/example/pubspec.yaml @@ -0,0 +1,24 @@ +name: quill_native_bridge_example +description: "Demonstrates usage of the quill_native_bridge plugin." +version: 1.0.0+1 +publish_to: 'none' + +environment: + sdk: ^3.5.2 +dependencies: + flutter: + sdk: flutter + + quill_native_bridge: + path: ../ + +dev_dependencies: + integration_test: + sdk: flutter + flutter_test: + sdk: flutter + + flutter_lints: ^4.0.0 + +flutter: + uses-material-design: true \ No newline at end of file diff --git a/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift index e5e9997ab..27d4f441a 100644 --- a/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift +++ b/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift @@ -16,6 +16,14 @@ public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { #else result(false) #endif + case "getClipboardHTML": + let pasteboard = UIPasteboard.general + if let htmlData = pasteboard.data(forPasteboardType: "public.html") { + let html = String(data: htmlData, encoding: .utf8) + result(html) + } else { + result(nil) + } default: result(FlutterMethodNotImplemented) } diff --git a/quill_native_bridge/ios/Resources/PrivacyInfo.xcprivacy b/quill_native_bridge/ios/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..a34b7e2e6 --- /dev/null +++ b/quill_native_bridge/ios/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/lib/quill_native_bridge.dart index a14a6d271..48815dae8 100644 --- a/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/lib/quill_native_bridge.dart @@ -11,4 +11,19 @@ class QuillNativeBridge { /// is [TargetPlatform.iOS] and [kIsWeb] is `false`. static Future isIOSSimulator() => QuillNativeBridgePlatform.instance.isIOSSimulator(); + + /// Return the clipboard content as HTML for **non-web platforms**. + /// + /// Doesn't support web, should use + /// [paste_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event) + /// instead. + /// + /// The HTML can be platform-dependent. + /// + /// Returns `null` if the HTML content is not available or if the user has not granted + /// permission for pasting (on some platforms such as iOS). + /// + /// Currently only supports **Android** and **iOS**. + static Future getClipboardHTML() => + QuillNativeBridgePlatform.instance.getClipboardHTML(); } diff --git a/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart index e4475cbc5..e6c5ed193 100644 --- a/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart @@ -1,5 +1,5 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; +import 'package:flutter/services.dart' show MethodChannel; import 'quill_native_bridge_platform_interface.dart'; @@ -29,4 +29,25 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { }()); return isSimulator ?? false; } + + @override + Future getClipboardHTML() async { + assert(() { + if (kIsWeb) { + throw FlutterError( + 'getClipboardHTML() method should be only called on non-web platforms.', + ); + } + const supportedPlatforms = {TargetPlatform.android, TargetPlatform.iOS}; + if (!supportedPlatforms.contains(defaultTargetPlatform)) { + throw FlutterError( + 'getClipboardHTML() currently only supports Android and iOS.', + ); + } + return true; + }()); + final htmlText = + await methodChannel.invokeMethod('getClipboardHTML'); + return htmlText; + } } diff --git a/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart b/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart index 406c530e3..063ea1cb0 100644 --- a/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart @@ -25,7 +25,10 @@ abstract class QuillNativeBridgePlatform extends PlatformInterface { } /// Check if the app is running on [iOS Simulator](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device). - Future isIOSSimulator() { - throw UnimplementedError('isIOSSimulator() has not been implemented.'); - } + Future isIOSSimulator() => + throw UnimplementedError('isIOSSimulator() has not been implemented.'); + + /// Return the clipboard content as HTML for **non-web platforms** + Future getClipboardHTML() => + throw UnimplementedError('getClipboardHTML() has not been implemented.'); } diff --git a/quill_native_bridge/pubspec.yaml b/quill_native_bridge/pubspec.yaml index 3ce54abb8..d0c472d6c 100644 --- a/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/pubspec.yaml @@ -23,5 +23,8 @@ dev_dependencies: flutter: plugin: platforms: + android: + package: dev.flutterquill.quill_native_bridge + pluginClass: QuillNativeBridgePlugin ios: pluginClass: QuillNativeBridgePlugin \ No newline at end of file diff --git a/quill_native_bridge/test/quill_native_bridge_test.dart b/quill_native_bridge/test/quill_native_bridge_test.dart index 5ad8c969e..5c8440d62 100644 --- a/quill_native_bridge/test/quill_native_bridge_test.dart +++ b/quill_native_bridge/test/quill_native_bridge_test.dart @@ -10,6 +10,11 @@ class MockQuillNativeBridgePlatform implements QuillNativeBridgePlatform { @override Future isIOSSimulator() async => false; + + @override + Future getClipboardHTML() async { + return '
Invalid HTML
'; + } } void main() { @@ -19,11 +24,18 @@ void main() { expect(initialPlatform, isInstanceOf()); }); - test('isIOSSimulator', () async { - final fakePlatform = MockQuillNativeBridgePlatform(); - QuillNativeBridgePlatform.instance = fakePlatform; + final fakePlatform = MockQuillNativeBridgePlatform(); + QuillNativeBridgePlatform.instance = fakePlatform; + test('isIOSSimulator', () async { debugDefaultTargetPlatformOverride = TargetPlatform.iOS; expect(await QuillNativeBridge.isIOSSimulator(), false); }); + + test('getClipboardHTML()', () async { + expect( + await QuillNativeBridgePlatform.instance.getClipboardHTML(), + '
Invalid HTML
', + ); + }); } From 013a16279621a119aa7bc4560208430138fdc5c1 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 15 Sep 2024 18:50:21 +0300 Subject: [PATCH 02/90] chore(example): use quill_native_bridge from path in dependency_overrides --- example/pubspec.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 572ced715..8fcd8f314 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -61,6 +61,8 @@ dependency_overrides: path: ../dart_quill_delta flutter_quill_test: path: ../flutter_quill_test + quill_native_bridge: + path: ../quill_native_bridge dev_dependencies: From 9397b6cf246a97de44cb08f98db14219e166172a Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 15 Sep 2024 23:13:37 +0300 Subject: [PATCH 03/90] feat: use quill_native_bridge for HTML Clipboard in flutter_quill and the example (initial impl) --- example/lib/main.dart | 3 ++- .../clipboard/default_clipboard_service.dart | 15 ++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 2a8ed52ce..badbdb124 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -30,7 +30,8 @@ void main() async { ? HydratedStorage.webStorageDirectory : await getApplicationDocumentsDirectory(), ); - FlutterQuillExtensions.useSuperClipboardPlugin(); + // TODO: https://github.com/singerdmx/flutter-quill/pull/2230 and related issues + // FlutterQuillExtensions.useSuperClipboardPlugin(); runApp(const MyApp()); } diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart index 4509c1100..503476d88 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart @@ -1,20 +1,17 @@ import 'package:flutter/services.dart' show Clipboard, Uint8List; +import 'package:quill_native_bridge/quill_native_bridge.dart' + show QuillNativeBridge; +import '../../common/utils/platform.dart'; import 'clipboard_service.dart'; -/// Default implementation using only internal flutter plugins +/// Default implementation class DefaultClipboardService implements ClipboardService { @override - Future canProvideHtmlText() async { - return false; - } + Future canProvideHtmlText() async => isAndroidApp || isIosApp; @override - Future getHtmlText() { - throw UnsupportedError( - 'DefaultClipboardService does not support retrieving HTML text.', - ); - } + Future getHtmlText() => QuillNativeBridge.getClipboardHTML(); @override Future canProvideHtmlTextFromFile() async { From e6ff099f67caff5331746712a88a9aede8515d58 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 16 Sep 2024 00:16:50 +0300 Subject: [PATCH 04/90] feat(quill_native_bridge): extend the support for macOS. --- example/lib/main.dart | 1 - .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/macos/Podfile.lock | 6 + .../clipboard/default_clipboard_service.dart | 6 +- quill_native_bridge/.metadata | 3 + .../example/ios/RunnerTests/RunnerTests.swift | 27 - quill_native_bridge/example/lib/main.dart | 4 +- quill_native_bridge/example/macos/.gitignore | 7 + .../macos/Flutter/Flutter-Debug.xcconfig | 2 + .../macos/Flutter/Flutter-Release.xcconfig | 2 + .../Flutter/GeneratedPluginRegistrant.swift | 12 + quill_native_bridge/example/macos/Podfile | 43 + .../example/macos/Podfile.lock | 22 + .../macos/Runner.xcodeproj/project.pbxproj | 801 ++++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 98 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../example/macos/Runner/AppDelegate.swift | 9 + .../AppIcon.appiconset/Contents.json | 68 ++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 102994 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 5680 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 520 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 14142 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1066 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 36406 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 2218 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 343 ++++++++ .../macos/Runner/Configs/AppInfo.xcconfig | 14 + .../macos/Runner/Configs/Debug.xcconfig | 2 + .../macos/Runner/Configs/Release.xcconfig | 2 + .../macos/Runner/Configs/Warnings.xcconfig | 13 + .../macos/Runner/DebugProfile.entitlements | 12 + .../example/macos/Runner/Info.plist | 32 + .../macos/Runner/MainFlutterWindow.swift | 15 + .../example/macos/Runner/Release.entitlements | 8 + .../lib/quill_native_bridge.dart | 16 +- .../quill_native_bridge_method_channel.dart | 7 +- .../Classes/QuillNativeBridgePlugin.swift | 25 + .../macos/quill_native_bridge.podspec | 23 + quill_native_bridge/pubspec.yaml | 2 + 41 files changed, 1617 insertions(+), 36 deletions(-) delete mode 100644 quill_native_bridge/example/ios/RunnerTests/RunnerTests.swift create mode 100644 quill_native_bridge/example/macos/.gitignore create mode 100644 quill_native_bridge/example/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 quill_native_bridge/example/macos/Flutter/Flutter-Release.xcconfig create mode 100644 quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 quill_native_bridge/example/macos/Podfile create mode 100644 quill_native_bridge/example/macos/Podfile.lock create mode 100644 quill_native_bridge/example/macos/Runner.xcodeproj/project.pbxproj create mode 100644 quill_native_bridge/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 quill_native_bridge/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 quill_native_bridge/example/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 quill_native_bridge/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 quill_native_bridge/example/macos/Runner/AppDelegate.swift create mode 100644 quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 quill_native_bridge/example/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 quill_native_bridge/example/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 quill_native_bridge/example/macos/Runner/Configs/Debug.xcconfig create mode 100644 quill_native_bridge/example/macos/Runner/Configs/Release.xcconfig create mode 100644 quill_native_bridge/example/macos/Runner/Configs/Warnings.xcconfig create mode 100644 quill_native_bridge/example/macos/Runner/DebugProfile.entitlements create mode 100644 quill_native_bridge/example/macos/Runner/Info.plist create mode 100644 quill_native_bridge/example/macos/Runner/MainFlutterWindow.swift create mode 100644 quill_native_bridge/example/macos/Runner/Release.entitlements create mode 100644 quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift create mode 100644 quill_native_bridge/macos/quill_native_bridge.podspec diff --git a/example/lib/main.dart b/example/lib/main.dart index badbdb124..b3a483f76 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -8,7 +8,6 @@ import 'package:flutter_localizations/flutter_localizations.dart' GlobalWidgetsLocalizations; import 'package:flutter_quill/flutter_quill.dart' show Document; import 'package:flutter_quill/translations.dart' show FlutterQuillLocalizations; -import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart' show HydratedBloc, HydratedStorage; import 'package:path_provider/path_provider.dart' diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index eabf85a3e..c51d61035 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,6 +12,7 @@ import flutter_inappwebview_macos import gal import irondash_engine_context import path_provider_foundation +import quill_native_bridge import share_plus import sqflite import super_native_extensions @@ -26,6 +27,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin")) IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 72d8d70f9..2d04e0a53 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -18,6 +18,8 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS + - quill_native_bridge (0.0.1): + - FlutterMacOS - share_plus (0.0.1): - FlutterMacOS - sqflite (0.0.3): @@ -40,6 +42,7 @@ DEPENDENCIES: - gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`) - irondash_engine_context (from `Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - quill_native_bridge (from `Flutter/ephemeral/.symlinks/plugins/quill_native_bridge/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`) - super_native_extensions (from `Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos`) @@ -67,6 +70,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + quill_native_bridge: + :path: Flutter/ephemeral/.symlinks/plugins/quill_native_bridge/macos share_plus: :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos sqflite: @@ -88,6 +93,7 @@ SPEC CHECKSUMS: irondash_engine_context: da62996ee25616d2f01bbeb85dc115d813359478 OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + quill_native_bridge: 1a3a4bfab7cbe4ed0232a17d8aae201a3ce6d302 share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec super_native_extensions: 85efee3a7495b46b04befcfc86ed12069264ebf3 diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart index 503476d88..79c884f74 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart @@ -1,14 +1,16 @@ +import 'package:flutter/foundation.dart' show defaultTargetPlatform; import 'package:flutter/services.dart' show Clipboard, Uint8List; import 'package:quill_native_bridge/quill_native_bridge.dart' show QuillNativeBridge; -import '../../common/utils/platform.dart'; import 'clipboard_service.dart'; /// Default implementation class DefaultClipboardService implements ClipboardService { @override - Future canProvideHtmlText() async => isAndroidApp || isIosApp; + Future canProvideHtmlText() async => + QuillNativeBridge.supportedHtmlClipboardPlatforms + .contains(defaultTargetPlatform); @override Future getHtmlText() => QuillNativeBridge.getClipboardHTML(); diff --git a/quill_native_bridge/.metadata b/quill_native_bridge/.metadata index 14c915b80..47e1e004f 100644 --- a/quill_native_bridge/.metadata +++ b/quill_native_bridge/.metadata @@ -21,6 +21,9 @@ migration: - platform: ios create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + - platform: macos + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d # User provided section diff --git a/quill_native_bridge/example/ios/RunnerTests/RunnerTests.swift b/quill_native_bridge/example/ios/RunnerTests/RunnerTests.swift deleted file mode 100644 index 890ebd3b3..000000000 --- a/quill_native_bridge/example/ios/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Flutter -import UIKit -import XCTest - - -@testable import quill_native_bridge - -// 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 = QuillNativeBridgePlugin() - - 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/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/example/lib/main.dart index 645553af5..60c4421cd 100644 --- a/quill_native_bridge/example/lib/main.dart +++ b/quill_native_bridge/example/lib/main.dart @@ -64,10 +64,10 @@ class Buttons extends StatelessWidget { ); return; } - if (!{TargetPlatform.android, TargetPlatform.iOS} + if (!QuillNativeBridge.supportedHtmlClipboardPlatforms .contains(defaultTargetPlatform)) { scaffoldMessenger.showText( - 'Currently, this functionality is only supported on Android and iOS.', + 'Currently, this functionality is only supported on Android, iOS and macOS.', ); return; } diff --git a/quill_native_bridge/example/macos/.gitignore b/quill_native_bridge/example/macos/.gitignore new file mode 100644 index 000000000..746adbb6b --- /dev/null +++ b/quill_native_bridge/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/quill_native_bridge/example/macos/Flutter/Flutter-Debug.xcconfig b/quill_native_bridge/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 000000000..4b81f9b2d --- /dev/null +++ b/quill_native_bridge/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/quill_native_bridge/example/macos/Flutter/Flutter-Release.xcconfig b/quill_native_bridge/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 000000000..5caa9d157 --- /dev/null +++ b/quill_native_bridge/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift b/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 000000000..e9e54736c --- /dev/null +++ b/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import quill_native_bridge + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin")) +} diff --git a/quill_native_bridge/example/macos/Podfile b/quill_native_bridge/example/macos/Podfile new file mode 100644 index 000000000..c795730db --- /dev/null +++ b/quill_native_bridge/example/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# 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', 'ephemeral', '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 Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_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_macos_build_settings(target) + end +end diff --git a/quill_native_bridge/example/macos/Podfile.lock b/quill_native_bridge/example/macos/Podfile.lock new file mode 100644 index 000000000..4aa130df5 --- /dev/null +++ b/quill_native_bridge/example/macos/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - FlutterMacOS (1.0.0) + - quill_native_bridge (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + - quill_native_bridge (from `Flutter/ephemeral/.symlinks/plugins/quill_native_bridge/macos`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + quill_native_bridge: + :path: Flutter/ephemeral/.symlinks/plugins/quill_native_bridge/macos + +SPEC CHECKSUMS: + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + quill_native_bridge: 1a3a4bfab7cbe4ed0232a17d8aae201a3ce6d302 + +PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 + +COCOAPODS: 1.15.2 diff --git a/quill_native_bridge/example/macos/Runner.xcodeproj/project.pbxproj b/quill_native_bridge/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..d129dcaa6 --- /dev/null +++ b/quill_native_bridge/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 48F6F64E1D3B068E354AED5A /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDE555F7B1A8B2AA27DD31B7 /* Pods_RunnerTests.framework */; }; + 588077B9B7948518FF16E803 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 830F57774208EDAC364B3ADD /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* quill_native_bridge_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = quill_native_bridge_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7044F50D32DDE7ACC263CDD6 /* 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 = ""; }; + 768FD25A9A12B26278FEED07 /* 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; path = Release.xcconfig; sourceTree = ""; }; + 830F57774208EDAC364B3ADD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 83FAAE953977A0111144FF91 /* 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 = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 9DF098AD994623996956FE45 /* 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 = ""; }; + B38D6AB7A0D79A6AA9DD018A /* 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 = ""; }; + CDE555F7B1A8B2AA27DD31B7 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F0079804D7BFAC0C2A27B325 /* 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 48F6F64E1D3B068E354AED5A /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 588077B9B7948518FF16E803 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 758560ED93CFEE0910386647 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* quill_native_bridge_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 758560ED93CFEE0910386647 /* Pods */ = { + isa = PBXGroup; + children = ( + 83FAAE953977A0111144FF91 /* Pods-Runner.debug.xcconfig */, + 768FD25A9A12B26278FEED07 /* Pods-Runner.release.xcconfig */, + 7044F50D32DDE7ACC263CDD6 /* Pods-Runner.profile.xcconfig */, + B38D6AB7A0D79A6AA9DD018A /* Pods-RunnerTests.debug.xcconfig */, + F0079804D7BFAC0C2A27B325 /* Pods-RunnerTests.release.xcconfig */, + 9DF098AD994623996956FE45 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 830F57774208EDAC364B3ADD /* Pods_Runner.framework */, + CDE555F7B1A8B2AA27DD31B7 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + EB8B52EC474BCDEAADAC6F27 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + C9E331B712FA33539C773C66 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + AE8E2B94E6C70C9A86E2E5CD /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* quill_native_bridge_example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + AE8E2B94E6C70C9A86E2E5CD /* [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; + }; + C9E331B712FA33539C773C66 /* [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; + }; + EB8B52EC474BCDEAADAC6F27 /* [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; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B38D6AB7A0D79A6AA9DD018A /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutterquill.quillNativeBridgeExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/quill_native_bridge_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/quill_native_bridge_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F0079804D7BFAC0C2A27B325 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutterquill.quillNativeBridgeExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/quill_native_bridge_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/quill_native_bridge_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9DF098AD994623996956FE45 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutterquill.quillNativeBridgeExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/quill_native_bridge_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/quill_native_bridge_example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/quill_native_bridge/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/quill_native_bridge/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/quill_native_bridge/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/quill_native_bridge/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/quill_native_bridge/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..bb3240123 --- /dev/null +++ b/quill_native_bridge/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/quill_native_bridge/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/quill_native_bridge/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/quill_native_bridge/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/quill_native_bridge/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/quill_native_bridge/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/quill_native_bridge/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/quill_native_bridge/example/macos/Runner/AppDelegate.swift b/quill_native_bridge/example/macos/Runner/AppDelegate.swift new file mode 100644 index 000000000..8e02df288 --- /dev/null +++ b/quill_native_bridge/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..a2ec33f19 --- /dev/null +++ b/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..82b6f9d9a33e198f5747104729e1fcef999772a5 GIT binary patch literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? zgxkP+ytd)OMWe7QvTf8yj4;V=?m172!BEt@6*TPUT4m3)yir}esnIodFGatGnsSfJ z**;;yw=1VCb2J|A7cBz-F5QFOQh2JDQFLarE>;4ZMzQ$s^)fOscIVv2-o{?ct3~Zv zy{0zU>3`+-PluS|ADraI9n~=3#Tvfx{pDr^5i$^-h5tL*CV@AeQFLxv4Y<$xI{9y< zZ}li*WIQ+XS!IK;?IVD0)C?pNBA(DMxqozMy1L#j+ba1Cd+2w&{^d-OEWSSHmNH>9 z%1Ldo(}5*>a8rjQF&@%Ka`-M|HM+m<^E#bJtVg&YM}uMb7UVJ|OVQI-zt-*BqQ zG&mq`Bn7EY;;+b%Obs9i{gC^%>kUz`{Qnc=ps7ra_UxEP$!?f&|5fHnU(rr?7?)D z$3m9e{&;Zu6yfa1ixTr;80IP7KLgkKCbgv1%f_weZK6b7tY+AS%fyjf6dR(wQa9TD zYG9`#!N4DqpMim|{uViKVf0B+Vmsr7p)Y+;*T~-2HFr!IOedrpiXXz+BDppd5BTf3 ztsg4U?0wR?9@~`iV*nwGmtYFGnq`X< zf?G%=o!t50?gk^qN#J(~!sxi=_yeg?Vio04*w<2iBT+NYX>V#CFuQGLsX^u8dPIkP zPraQK?ro`rqA4t7yUbGYk;pw6Z})Bv=!l-a5^R5Ra^TjoXI?=Qdup)rtyhwo<(c9_ zF>6P%-6Aqxb8gf?wY1z!4*hagIch)&A4treifFk=E9v@kRXyMm?V*~^LEu%Y%0u(| z52VvVF?P^D<|fG)_au(!iqo~1<5eF$Sc5?)*$4P3MAlSircZ|F+9T66-$)0VUD6>e zl2zlSl_QQ?>ULUA~H?QbWazYeh61%B!!u;c(cs`;J|l z=7?q+vo^T#kzddr>C;VZ5h*;De8^F2y{iA#9|(|5@zYh4^FZ-3r)xej=GghMN3K2Y z=(xE`TM%V8UHc4`6Cdhz4%i0OY^%DSguLUXQ?Y3LP+5x3jyN)-UDVhEC}AI5wImt; zHY|*=UW}^bS3va-@L$-fJz2P2LbCl)XybkY)p%2MjPJd-FzkdyWW~NBC@NlPJkz{v z+6k6#nif`E>>KCGaP34oY*c#nBFm#G8a0^px1S6mm6Cs+d}E8{J;DX=NEHb|{fZm0 z@Ors@ebTgbf^Jg&DzVS|h&Or)56$+;%&sh0)`&6VkS@QxQ=#6WxF5g+FWSr7Lp9uF zV#rc`yLe?f*u6oZoi3WpOkKFf^>lHb2GC6t!)dyGaQbK7&BNZ7oyP)hUX1Y(LdW-I z6LI2$i%+g!zsjT(5l}5ROLb)8`9kkldbklcq6tfLSrAyh#s(C1U2Sz9`h3#T9eX#Hryi1AU^!uv*&6I~qdM_B7-@`~8#O^jN&t7+S zTKI6;T$1@`Kky-;;$rU1*TdY;cUyg$JXalGc&3-Rh zJ&7kx=}~4lEx*%NUJA??g8eIeavDIDC7hTvojgRIT$=MlpU}ff0BTTTvjsZ0=wR)8 z?{xmc((XLburb0!&SA&fc%%46KU0e&QkA%_?9ZrZU%9Wt{*5DCUbqIBR%T#Ksp?)3 z%qL(XlnM!>F!=q@jE>x_P?EU=J!{G!BQq3k#mvFR%lJO2EU2M8egD?0r!2s*lL2Y} zdrmy`XvEarM&qTUz4c@>Zn}39Xi2h?n#)r3C4wosel_RUiL8$t;FSuga{9}-%FuOU z!R9L$Q!njtyY!^070-)|#E8My)w*~4k#hi%Y77)c5zfs6o(0zaj~nla0Vt&7bUqfD zrZmH~A50GOvk73qiyfXX6R9x3Qh)K=>#g^^D65<$5wbZjtrtWxfG4w1f<2CzsKj@e zvdsQ$$f6N=-%GJk~N7G(+-29R)Cbz8SIn_u|(VYVSAnlWZhPp8z6qm5=hvS$Y zULkbE?8HQ}vkwD!V*wW7BDBOGc|75qLVkyIWo~3<#nAT6?H_YSsvS+%l_X$}aUj7o z>A9&3f2i-`__#MiM#|ORNbK!HZ|N&jKNL<-pFkqAwuMJi=(jlv5zAN6EW`ex#;d^Z z<;gldpFcVD&mpfJ1d7><79BnCn~z8U*4qo0-{i@1$CCaw+<$T{29l1S2A|8n9ccx0!1Pyf;)aGWQ15lwEEyU35_Y zQS8y~9j9ZiByE-#BV7eknm>ba75<_d1^*% zB_xp#q`bpV1f9o6C(vbhN((A-K+f#~3EJtjWVhRm+g$1$f2scX!eZkfa%EIZd2ZVG z6sbBo@~`iwZQC4rH9w84rlHjd!|fHc9~12Il&?-FldyN50A`jzt~?_4`OWmc$qkgI zD_@7^L@cwg4WdL(sWrBYmkH;OjZGE^0*^iWZM3HBfYNw(hxh5>k@MH>AerLNqUg*Og9LiYmTgPw zX9IiqU)s?_obULF(#f~YeK#6P>;21x+cJ$KTL}|$xeG?i`zO;dAk0{Uj6GhT-p-=f zP2NJUcRJ{fZy=bbsN1Jk3q}(!&|Fkt_~GYdcBd7^JIt)Q!!7L8`3@so@|GM9b(D$+ zlD&69JhPnT>;xlr(W#x`JJvf*DPX(4^OQ%1{t@)Lkw5nc5zLVmRt|s+v zn(25v*1Z(c8RP@=3l_c6j{{=M$=*aO^ zPMUbbEKO7m2Q$4Xn>GIdwm#P_P4`or_w0+J+joK&qIP#uEiCo&RdOaP_7Z;PvfMh@ zsXUTn>ppdoEINmmq5T1BO&57*?QNLolW-8iz-jv7VAIgoV&o<<-vbD)--SD%FFOLd z>T$u+V>)4Dl6?A24xd1vgm}MovrQjf-@YH7cIk6tP^eq-xYFymnoSxcw}{lsbCP1g zE_sX|c_nq(+INR3iq+Oj^TwkjhbdOo}FmpPS2*#NGxNgl98|H0M*lu)Cu0TrA|*t=i`KIqoUl(Q7jN zb6!H-rO*!&_>-t)vG5jG>WR6z#O9O&IvA-4ho9g;as~hSnt!oF5 z6w(4pxz|WpO?HO<>sC_OB4MW)l`-E9DZJ$!=ytzO}fWXwnP>`8yWm5tYw`b1KDdg zp@oD;g===H+sj+^v6DCpEu7R?fh7>@pz>f74V5&#PvBN+95?28`mIdGR@f*L@j2%% z%;Rz5R>l#1U zYCS_5_)zUjgq#0SdO#)xEfYJ)JrHLXfe8^GK3F*CA(Y)jsSPJ{j&Ae!SeWN%Ev727 zxdd3Y0n^OBOtBSKdglEBL)i5=NdKfqK=1n~6LX`ja;#Tr!II$AAH{Z#sp%`rwNGT5 zvHT%(LJB+kD{5N}7c_Rk6}@tikIeq%@MqxX%$P!(238YD(H<_d;xxo*oMiv^1io>g zt5z&6`}cjci90q2r0hutQXr!UA~|4e*u=k81D(Cp7n{4LVCa+u0%-8Uha+sqI#Om~ z!&)KN(#Zone^~&@Ja{|l?X64Dxk)q>tLRv{=0|t$`Kdaj z#{AJr>{_BtpS|XEgTVJ4WMvBRk-(mk@ZYGdY1VwI z81;z(MBGV|2j*Cj%dvl8?b2{{B#e0B7&7wfv+>g`R2^Ai5C_WUx|CnTrHm+RFGXrt zs<~zBtk@?Niu%|o6IEL+y60Q>zJlv``ePCa07C%*O~lj?74|}&A0!uA)3V7ST8b_- z6CBP1;x+S@xTzgOY2#s%@=bhZ@i@BwmS)neQG&=9KUtRf^K=MvjC5JnqLqykCE_P0 zjf#V4SdH2#%2EuDb!>FLHK7j;nd6VLW|$3gJuegpEl3DZ`BpJU$<}}A(rW?<6OB@9 zKP9G3An?T5BztrLdlximA;{>Tr7GAeSU=^<*y;%RHj+7;v+tonyh(8d;Izn}2{oz& zW)fsZ9gHYpI?B|uekS3zHUue3mI zb7?0+&Zm>Kq(F>~%VYEn)0b32I3~O^?Wx-HI|Zu?1-OA2yfyJ;gWygLOeU;)vRm3u z5J4vDIQYztnEm=QauX2(WJO{yzI0HUFl+oO&isMf!Yh2pu@p}65)|0EdWRbg(@J6qo5_Els>#|_2a1p0&y&UP z8x#Z69q=d663NPPi>DHx3|QhJl5Ka$Cfqbvl*oRLYYXiH>g8*vriy!0XgmT~&jh3l z+!|~l=oCj<*PD>1EY*#+^a{rVk3T(66rJ^DxGt|~XTNnJf$vix1v1qdYu+d@Jn~bh z!7`a`y+IEcS#O*fSzA;I`e_T~XYzpW7alC%&?1nr);tSkNwO&J`JnX+7X1Q8fRh_d zx%)Xh_YjI3hwTCmGUeq_Z@H#ovkk_b(`osa$`aNmt`9A#t&<^jvuf z1E1DrW(%7PpAOQGwURz@luEW9-)L!`Jy*aC*4mcD?Si~mb=3Kn#M#1il9%`C0wkZ` zbpJ-qEPaOE5Y5iv_z%Wr{y4jh#U+o^KtP{pPCq-Qf&!=Uu)cEE(Iu9`uT#oHwHj+w z_R=kr7vmr~{^5sxXkj|WzNhAlXkW^oB4V)BZ{({~4ylOcM#O>DR)ZhD;RWwmf|(}y zDn)>%iwCE=*82>zP0db>I4jN#uxcYWod+<;#RtdMGPDpQW;riE;3cu``1toL|FaWa zK)MVA%ogXt3q55(Q&q+sjOG`?h=UJE9P;8i#gI*#f}@JbV(DuGEkee;La*9{p&Z?;~lE!&-kUFCtoDHY*MS zzj+S$L9+aTs(F^4ufZe6>SBg;m@>0&+kEZMFmD*~p~sx?rx=!>Ge;KYw<33y#*&77 zFZI`YE(Iz?+tH;Fq;y=MaSqT{Ayh*HFv0(z{_?Q+7@nE%p?S8%X6c!+y;!0NLXwJV8Co_}R3*7>n+oMsQpv8}8ZS-P@(Rg|gmxZHzf=nMOUAAY}AZGfWVzZjE@4$=7xkIrs8BE%606aVU%kxz_04ipig51k& z(>c9rJL2q%xvU%Zj#GR9C9)HLCR;#zQBB@x;e_9$ayn(JmSg_*0G?+wOF?&iu@}S{ zt$;TPf*Lj$3=d<}Q3o!Hq@3~lFxoiCyeEt}o3fihIn{x2s1)e2@3##&GYDq~YO|!q zUs0P-zy)+ohl-VQ`bhvUpC{-d$lkpML_M%Kl6@#_@A}w{jWCDsPa#cSbWA#C4Sf|*C*&Z{ zz?hOU7Cc`?>H$WGqITA2P~fYudnQHxB8^;0ZFKC;19F#~n_2P@{cE{Czq-#K5L_8| zc3aOEwq4%zL5>YU_mc9fc-p~{fBTWUkxTiZvxt9FOqC{s#TBp(#dWc+{Ee{dZ#B!g zHnaOJ8;KO1G;QU2ciodE+#Z$Wuz*Hc6NRO!AUMi|gov=>=cwcZeL&`>Jfn!35hV1J z;B2@0!bIR853w%T*m6)gQ?DPnQ)o6EtKaN3L;o?*q<83d&lG&U=A|6hcT?f0)4h6{ zGIZ0|!}-?*n{zr}-}cC}qWxEN%g60+{my)o^57{QEn(tSrmD7o)|r0+HVpQPopFu; z0<S}pW8W2vXzSxEqGD+qePj^x?R$e2LO&*ewsLo{+_Z)Wl|Z1K47j zsKoNRlX)h2z^ls_>IZ0!2X5t&irUs%RAO$Dr>0o$-D+$!Kb9puSgpoWza1jnX6(eG zTg-U z6|kf1atI!_>#@|=d01Ro@Rg)BD?mY3XBsG7U9%lmq>4;Gf&2k3_oyEOdEN&X6Hl5K zCz^hyt67G;IE&@w1n~%ji_{sob_ssP#Ke|qd!Xx?J&+|2K=^`WfwZ-zt|sklFouxC zXZeDgluD2a?Zd3e{MtE$gQfAY9eO@KLX;@8N`(?1-m`?AWp!a8bA%UN>QTntIcJX zvbY+C-GD&F?>E?jo$xhyKa@ps9$Dnwq>&)GB=W~2V3m)k;GNR$JoPRk%#f3#hgVdZ zhW3?cSQ*((Fog26jiEeNvum-6ID-fbfJ?q1ZU#)dgnJ^FCm`+sdP?g;d4VD$3XKx{ zs|Y4ePJp|93fpu)RL+#lIN9Ormd;<_5|oN!k5CENnpO>{60X;DN>vgHCX$QZYtgrj z*1{bEA1LKi8#U%oa!4W-4G+458~`5O4S1&tuyv>%H9DjLip7cC~RRS@HvdJ<|c z$TxEL=)r)XTfTgVxaG!gtZhLL`$#=gz1X=j|I@n~eHDUCW39r=o_ml@B z0cDx$5;3OA2l)&41kiKY^z7sO_U%1=)Ka4gV(P#(<^ z_zhThw=}tRG|2|1m4EP|p{Swfq#eNzDdi&QcVWwP+7920UQB*DpO0(tZHvLVMIGJl zdZ5;2J%a!N1lzxFwAkq05DPUg2*6SxcLRsSNI6dLiK0&JRuYAqwL}Z!YVJ$?mdnDF z82)J_t=jbY&le6Hq$Qs}@AOZGpB1}$Ah#i;&SzD1QQNwi6&1ddUf7UG0*@kX?E zDCbHypPZ9+H~KnDwBeOXZ-W-Y80wpoGB*A) z_;26Z`#s0tKrf~QBi2rl2=>;CS1w)rcD3-sB!8NI*1iQo59PJ>OLnqeV4iK7`RBi^ zFW{*6;nlD&cSunmU3v4JKj|K4xeN(q>H%;SsY8yDdw5BJ75q8>Ov)&D5OPZ`XiRHl z;)mAA0Woy6f!xCK(9H2rq?qzp83liZAIpBPl-dQ&$2=&H?Im~%g;vnIw1I+8q|kr! z36&^9}CMmR(U2rf|j12oG=vb%Ypsq8u9Kq}U*ANX*)9uK}fAi8;V_7Z;0_4*iydDxN-? zv?qJ=T*{MzL~-xUv{_Kh_q9#F{8gPV!yPUUS8pEq*=}2-#1d=sC_|U-rX~F0 zBLawgCWy#?#ax{~DAnDvh^`}wyUO`ioMK~jgh%L7^}#h?beSyvQ_g>+`2`}`-1h7# zg*?qJdm=53hwN8~B=^|LPmYtOVrQ(W{sNm4uofq=4P@dUA%$onWbw_m-KWia&n9iv zi)!9#OJ#^}eg8tE{wSb9(c0D^PS1 z9EBS5*ypSiVRS_G0v?$hyoZOS7hFWlp4qbYkf9Y&{%OzhsIdHskLptn96@k6@^K@U zszd8POehITDK+AyW#JKpnWY;ju#MC$JjB1Y*~(E6N%{p#kO+bVxG3X<34n3fW=k{A zCZt|KP%x^GQ9%mU)KE0{LA=vaZvRQbxSlK~eAkwWo2Z<{j5eS5NVTMe`m%re8%~7K zZLtU&b~YDN%~uA9wPf>x2=PI=MA6_oVe>Ek$s5&&Z=8vvF5EODP4Av(b|dlNgF1O8 zy83W0WRdzjz2iNA~t1piEqlyU&`$yZtqR`6X_PmuP>W+D|8iH;FQ zN{JuU#Tz9mV=4R_IewROL1|mK^`lLat#LcIBfggzM(iO$pQT*-c_ z94^LUWw#5B9~sp2W1p`c)Y(xfR<{O^9n4E6vDDw{#-R4UMBKo{>Hqlqn*a9rl_>+0 zS5MwJC~nCC`1X%VCyWFsiDX;bfAJQAUkU#105f_s5U-8rqO}n8fA1{b>Fr6Q|Ea(V z5B11Lo^ooWF?`^{-U#?iatokWI-e$632frzY?Yzzx(xJc@LFM4A~-eg!u|tl{)8Nx ztZLXsSC*68g%9TFu(f&J9nmc^9hgyy#uUOMJFCaifSaDcyQ&6=8e9=t zIFEAQ{EK{|73{($!a4=!wj4ABcQrUQp#+gGM?wEUp(w@+Fzi{!lt}|3`PM%&d-seeR zB$}BrFGD3R10CE>Hsb>;PrP}pd` zaY4}6+Wu(`#uAV+E5SV7VIT7ES#b(U0%%DgN1}USJH>)mm;CHPv>}B18&0F~Kj@1= z&^Jyo+z-E)GRT4U*7$8wJO1OibWg0Jw>C$%Ge|=YwV@Y1(4fR>cV#6aGtRoF@I`*w_V4;)V231NzNqb6g@jdpjmjv*<2j02yU$F8ZS$fTvCC`%|Yn#x< zXUnP&b!GLpOY-TY3d?<-Hhxom_LM9`JC9LEX2{t1P-Nj%nG+0Vq)vQwvO^}coPH-> zAo8w#s>Je^Yy*#PlK=XDxpVS~pFe-j#jN-(As&LRewOf(kN-aKF(H+s*{*!0xrlZw zchJu@XAvQWX7DI1E8?F}Wc8m46eT+C<0eXVB+Z^(g=Kl@FG-cn@u$suj)1V2(KNg_ zh29ws6&6(q~+sOAoHY^o86A<#n*?Pg2)cK$+y;cY$hJLq4)4V84=j+3ShSr##Tk5kgmxB zkW+8A1GtceEx~^Ebhwm36U?oA)h)!mt=eg0QE$D1QsLNZ_T3NH?=B&0j~#298!6iv zhc0|-{46*3`Rx&nKSXnf1&w-Rs>#PGAGuY@cBTU-j|Fxbn3z49S#6KBaP^Lx*AOXxIibr z!1ysMi(&kr!1wwQB5w`BDH2~>T4bI`T1}A2RM0zd7ikC&kuBRsB`Z2@J!Udm{AmSN zrr0k6_qCZL**=)xRW`MFu(OY=OT;3G8eF~ z2mmkXZ9X(sjuKmq+_<=LSjphB$~R1o^Yb=rO!j!(4ErIox^x55o{pXSE9X$!76^*$ zoKhlAX6y%n^U=C~@!vIlEgXQGD@>oOU=_(aXF-Sjas*$AKESfRzxQ8#3yOj|y0OCU z>6Z-0%LCcjla&7I+CXm&caKp@@jQ!5M`(_{CL=@4#JJ}cHeZw>^b6fpv269LSV?gV5Q{kk?4;;y9RIsy5vk%DIRiL(9xe1aA@4!VX zDh2}xgUd5X?6nji%&7-%QuyKSYA-Z{PwJijUQ}In+EJl|x@dF1P<5bPa5W3&&?^h$ zZCo8LepKo0a(Fsln*cHL;D(gu9MMkoiM0*n31u)jHqX5x^F95tnI&^}^yKx3YwEm@ zo8?EZ710ykx@19{=yz5IXb8w4yjdveWb{IVL6Z(Cs>!a_0X^1E27o!4e&b43+J*u2Gb(59k2uK0goLwhO{ujLS ziI9LA9`&x~Y$6JNX!aEXR``}LUI}Gr#=<^wBHmg%v<)zRWDVtq)kT$-P7iU1R)2XZ zi~bYhV@EZ`@prgK(cs{>2jn$pxg$<|KjJ7%26Km>%KcXh^bU@y@V_Lf@=j1x%R4{v zOcQn{I}!2W<~08FOVnoV>zOTH=+>v9!jFo|q)ucqIe!N4{U5_G`>>*sVD{8I~4FqyU8imZ**-Gy`~Xd z4w35GMf%7^i65HdX{Iz|f2Kg193#KhPIeR)-=eYx3Z!%RM=JjwLrdk^B#6rg!ym2w zPbFqYyO4>W_Z6PonAwiu7?!h=x%sR-T+_*xZOGh2wWhWr%}%2^$$ zQvACIB~pi=m|`hXIMvoq`TOCx=J_D2>pi6$NPy3&8#vy|oX)=kM0Z}$BR$r0G}MzOk-OqG+VmZtOZoj6x4(tLh|5h) zBv64Y{DPHsy&_H(5_l(&Y}FhVvr9m_*_Q~Zy-}V9+VmGnvndEjYW4qt4K~N&Y&6g| zfpz*V=A#^mVmuOAz)(KVI<%v5NY0%Goy!{9&o41upsPWk(yFuRP|A4q6NMnX%V~MT zi_Rb-Bno2kI+j0Cw`@ydy{e%ARS#Z%b6I%_yfo_ZKXr4BLVoHzBKJ^ZG z-2>2IzU)55@9C|?_P$ew^-7zEiAKG1XAi{!3h%1m#9s%^pGy6S9wKFYY4<$djeoJP z{GI}Vd%idY$4_fh(7NXm7#;cC!DS&-{tGr!Qze{^%bUx2jgG@-kMta^q-EwrKB}d8 z{%FT>rFk_bzW<{lc%eYlrsiYTZXGgzD1&lmRyp+c1O=0=zAX=KV62bx-a~JP{cPF4 zU$-XT#(9&T>l@bMu3nSr{)%-5lV+0t&bxip4DVJ~vlL$J2P6X~ zd{FS8vm{Lhrieul*7&(AgPuXhjpGila%6_?-+k#b)cdk#M1jB*nE>G6NGOr+Ek{`= z9b%S1`$`=g0CC$>0$Db;l_szReLYVmce*(()9%Zz1`*fNXhI*oRlerWHarD(v^W^c zuc1Vuw6Gbp7ZsoRH>QGt#&lv;5G~Ovt$%7VFd*-rN2>UjbOWBFGNGO`bru7CFB4tn zL`^?69Lj_g_TA&`9`dSI8s|)K|QM0 zybvV7!>xDY|6c6y;Q}qs`){1+WQu_5Dgd8Qe|q}}bxjH+joQQtqs1IVZn6{e7T{ia zF|=^xa%eWO%(x<7j*QZbcU_;aVaVP!arexOLOtoSNt*hvsRL%}%)jPetSich(`b-^ zMZ$PM9%s@%*jPVz0Z^W*cK_>G4f}+eEVX`HOaHg#!B`<4v;x}zDLMR*M27`kNfp!! zOfdt(>k-g>7jf^{Se@3$8<+;R*cYtw+wD_Z8Pl~!JDCUEPq{Ea*!J9`%ihyNJZ30i zmfve}S5<$Uso}_?SuI$ks|{-ddGLu9WR9`^9)Kdi@Vs;x#SY-xp}wHPU0|vEA7234 z@BN1z7OF=OOQtPF$4twn3!HTVlUVD_)ubMM7PEPoiC6lQgL2q9PK4~e8v-OuH%lie z?NgBLkIdPMG$QBq(>r^AOHB`|*1#*!2Z? zuU8H|FD`OBRu^(R?Z-Vhr0j;FLpS~a34KREnd}B=EYHS*>Hm+f%tgJt!4J8Q`qn^4 z9F=tO#JRJ}tzA`vx$nZ)O%wC?Uiv0+_nz}5Lj4ki*&=K&*#U`=rv z`Q@Q{+IhAj@6lrNK2B=8Yln!O2%zomfRehFT~;!O@(@Xy|1Jlw*uOB-M$#6K^)QBm z_7%#QVUDPwnW{iOV-grMQQU|3{=BQMh}c5(yMGdoQf*)k9-B zMQ(^GdJh+y)>qJprknS!%WxqM>HlHOP#7UVdy>%PW$!l72J`n-p7j(DBKoGxXWh(Y z>BFDZl|7knU_jg_SSbvFk8)39%2)Hu5W0}HKlh>EaqvFoXI&56Yy)3) zQkE4X^P0QnPn?iUUVHJZXzPp`s5uv?pG{K9IgGoHvcmlBxubi|iF7n{)mhenIcxGs zgr0OpQy#Y#u=5lOyiECfE_Sn?Fj1LyoRKcbTgX{p<T*v!CGkPc)pcA2D=4Ekp0Gb*wpy7S88C%Ywsbr?MI(3UdsCM?XJ1X%*hNjB)XqZ*W(qDdtSb z<3XN74ARXL3=c^bfW~F%NM^5*Zx92>Wq`&M625p~j$8mYwLbk%Kf)jbn#<2z$%vP5 zy#b>-tF-S2_AB4;R^K&^-1LJrUmi@9rB^FLF)-k&YHK8P+k@RCJ1qSTZ@=kHxA3l$ zmK_ZG)l6(nmCR1a8|;QF-B5e_ELnjJ1$m-;4UXX?WytF_wz7#&AjwZYTMVieLbq@R z3t-q|G4^BB#EpNu4uyfDebB+-uu_$9>y-dzB30Y9F=R zrW-Heqnj*InPTWHgR9v^R7~hokldh&h8=HDhMW(EFfim1*{)5Lc1-+eBVkK-2!u=N zuZKABgJs3I--NbjE;>Undg6uK`^U>AQ6V zhc!RhYgvrmeGNsftr+(C<_MtuV$`5RZTf#5r=DR?gWG->#})#=(td%C3`oO+2B7im zUqY}&a_QNTn?s+?=mNXiREN%x_=(H)L|DtYPY>SR3pQfBOel7G_jR_{!9`dSj8Up-`JgcB;=Oor)U=_EVjF3C5{Sqh8cq=~bRjoBpoc$kJCgtTyZGSpQ4= zYi$6b$-dGmuTDF&@amhV?cU05g(AZV&v2$4m&j_~GZk;&keSO(@LRESRZ&p`dV*6w z2$em~p*8yM6j;SYorw`M5K2mluJq7P5Yn$VtZj8DEs2Zk=O@4T&Q}>~f31Z{uk}`E z{Dp{KObh1kk~~MfLUod72{Pk6G@T$_0_N??lOrdR=Z;VV#m0l)&@hz{Z?)@sgImi-&i1@95g53rON83v!yVPDHRU*Mzc4yZ(-Fr z{8{WXmIJf7jeswk$;6s~Qac6QyM3W&`}m#gRt=rr95A+Ad&wSAgvXZ|F))rBJVJ5W1CsjN`QaOzct2ocq#0!v zmj#075)C!3oS>&N;aHS@<+c>RHL)8j^p)k(8#7$LEx!1g_1^02!4_qA=;uhKW=+ix zGX%+vBMiRiF^^jm{mdO(?GdWJ#unO#_F^7mhT8)s(z_WlwFyJ#Xh)k5+RG2f;LC*K**1dr`#}~6A=0B=I&V;%zDA1)d@G!X#Rng)7G*2k8Kg447r0ox> z5NK`d(H-afBwo9feDOUi>;BbPsu!2|=@g=3j*PY}@YrOb+SX6?#Yb2xaaK!?>SX1J z_!VsB`2n1=wwSftkydm!39|-1?c%Epx?TO<(#GO~I&{f4+)XwRk<7RQ1~5>QcKH|D z?!}j1ueO0Lk;FZ{k4FA_(S`Ot0w~tl&m0duID*f6RY#bkw||o;kZ# zISYNTb|{~|X$m$Q-Jv#uxyw)eM0gIv`V#wOAp&Vv@>X4_tSZ&L#juM@$S9 zx_X_tLh<_^-F;LAQ09s@sPb%PMTrcw*HUV0P=RYSlM&AXEOI&&R&YCm_S<7DRBx^L zA^R^iwW+LMk(r*$Pq-fKU5X@=mQ=`ErO30H@@&qqnI7zJcrbSh+H<V ze&7Uli0xj@WrW#&-9%*FP~kPYF_YYM_hs5~|ExMynQ%qvq`leRB6W0yhC@pCb8>_P zlf=F~WMv_u*-DV=UaVu#2rlzK{q8D95VwZrfV?gj@rSNWXFvktUq)V5+YrlxwX302ae(;aG4e>L-M@3J+-f3IT{b9l!kg*2M zC1+ND9}6m^()LE87Mt+^Q|)!y#suc&v26C=0W88%a{?)E8Yvo@kM&KNMaOst#|-_CbUTm}WS@-c>nRb;&z^ zYr)+IE$1=jov(CZ%3uR+`~NI>1&Gs6W(jaamjcN$a`2!*nO}l|b%?)Q%%UWzw>A`C zR@px(P*7j$TK?jbv*%x)e^|jcLsv}aF(Z0=7(%Oa7+1wY>{B>d+i&ZA$}k(qgZPZY z;VkW~8eWnU&HPIAbco?&tc2O1$6=7n{u|^Y*nXoac{o1W-6aXfy~KlNbJfLoq~6;+ zDYmnv--Fhqrl+UV#k@_(1=gWNtqhyVKN=9CZ-{Ohi>e=~bm4IKbhM%%W zW8oXE!rGpV7Wt(_^4nndH1_imheaWzDi|I})9ZVZ9>pN+P%dVc5wG`Ze*4`@rjn1^ z`ln(;vPBHQUb}y8S>=8q__r7g+=z$>!pReVB0@XKchAvyGjLQs-u>+w%`frV4FeIG zj=7n~hGrwx*&5aHy(7X$bDZ7YhcP%(*>G^lAYMK;qG~V8Jz@b7oNg;IA1z$9@TbzW z;@I51@Ekef#qbxnG$Y8Z%bm~ibZ=4#%yKr%#b)CDrfKN`ujIY?tA4h9)i~dZ4E;ZM znvb$n2)zn$Wx&zlW%mJZDh28ox$@%`w3i7YFepXUChw}$UXKI=-TM51`M#FH=tdr*mQ!c=aB1296Lu>iTTKZWss0f z5~ihdImPN$aTle_AdbYC^31}_^EK|9R&l#%3hbx;8vJ+Gp^tm{9JDILu*1PW!rh^Dn9p<)h#Sl4kKM%nm<+!ESSk* zC;lLNT$fgr-!+{aBsSx$41b}yy6o>r3F#1&iv3cfY2N<+`0qJ+>=&Qxs}JOEkD?^l-F5i`t5+zNuvJf z3Fh4$mNqiFXL-aq4U4K@Ae$fq-TDT`rvrx;gqx96w^*@s=mcthCaIyPe(w)6kI{EqV10tcShHU9eeAPs)s?6#vrq}>y3FeTJu$Udha+z zs7}rmA@yR(L&>35sNjQqrw}o^)UitMU!5g6nnG)(tgst!^`FKJEzI1(d@j_w@;^hr zgYxlIRYjho4U$bhczfq&YySCqCE(5_d>l(4tk1v9!V7PB%Vx{QO=G2NC@c1%3rEzw zN<6i?h;CJX>h)kn49Sr)g#Em6km6ESP`1qc5C3ZHizN>r>V-fSS=X1nT{+Thh@kC! z(H=PlqDt7V6gOYezXUK-dretz!1?IUD6&eL2b!4=9h+HUO&DYZKMM>|YhlEEg?q?S z^XT4$2Fd|zT=x3U#L1|F;-#`to-Y6hiYkWdO=rRC)meY72pIfl`3zEGDU8($iWR^K zI$nq80aSJII<;#W5Pj>^_T&013BJ*O89Uoq z5>;Paa^E}xar^r=!pexg&OTM8wluk4R~Ru=)Hgk`Y#i_$jk{jc8hx}?(dW*X!l4vs z6_%$s#duJJFmaFc-5#>v6Yea=I~)s_pXGS>Tkz?s+WS}>Qp<9MappMLXpkXpSM~SmH6u)`Z5>o02kJs;w@KhdiZ3}29y*xr|6tMo zBHzGic+b+dTd!xOJ;p{Rguh^corJ;K?R6daayQKm+0rf7|AXg0qs!R9eS7t4{G=fs z1$=?kK1Ih=gEkI>@jgXDWHZt*C7FUEWs|u^pE3Z``^K|1KEC^sbN*4nQUfRc_AyE0 zn)?RrGjgPkzfE~_s!rDB!fDsV+*|kEX4+DyS#8%!cshn;s8svwBXSsDGX2ZRa0={* z=`p1F{zD17*Rk>Uk_cw3t5j=9-d6$}MoM~z{v{t^M!g75-+o8_XkP@CZWUQ2z!^26 zCNOu~hgrrK)y>bgqb{`Q_1^zrG4;cGarP!nb4E~(ZKWc`LVeEq;IewVneLp^ZU2+% z95PgN*M5v7Q;ZlGvM#`&u2NdHm%&gZ{bZM5wBCp&?HeZhwU87wyT_z!n4z+1?=RvXZ^72d*%+R1s1$KbAFtR|= zw;MEq=O7pMIKpFwKH6$OOszJAf<_Z<1)36cB>D>|Z6$gJL~jH`n3MMou$#Si%rDAu z4pSkJspG|^CJ86vg6kkfXsA_`8@8iOryOe!Qhn8SV6}mPlof3=WJRVqAr_b;e->`Z zMR(p|K|$L0^6;u~USxg#B6-ZNc%E1dv*^P=|2k*^NOBni#G%9Y?##{=)8KZwh85OL zSBG9|gb|hdmY^gn(ziY&O5#@I?W)W;361Yb^VQNpz0A7&^(7HRAsUvw#)fvhocvja zLxV65J0_$>&cVRctJFsn^qLos^tG`+B0_gQ{NeOwKt-!C^gGFufdtPT*Vi>l#X1|V z2XxsAcixN)Ekq=a##_^=k_^BFH5_zpvPDRP>u6+3$}i&b zy0@FdzAHw?i9OqnlTts_w5D@Nd#eM)KKEuN#m{|AJyscxa}(eA?z4&4yvXo{OBS65 z-?gW;<+;+ntM}U_yTmHm6*2zj0Imj<&ZgE9Wj|gfsXhrVH-c0p$7HXnR8bxDYOi z=_r3FA~u`L&2;Vir8}P3)k|@c?sK1U@&iWo{HEXcoy>6wQSuJ+b4l%aTBuigs&k@Y<2c=S3Ef?p zH>ki4yDuXdo_eu>X1{E$g(Q-u#zVXN^&%70guoizo7x(kQ0OZ}H$O9UB}(FaX8Ct1 zFpx~}EbHf2r6V;x=@8GH$C2|6*?K~?LrtMYd^bw*WYXhA z_))@RMH;nZedW3+qfWbv<|_#BYOxX^rhbN+!za)|!|8K*LRs(R$O*2SDM{g9k7e{u zN4VIdi}e#0&h?sBxu$>Yy%)j(k1V2fuhp8r!}gfF@b;F?U`6}YnnMh1&sSU&lR^?# zu!61+lGsuFEfDraX3+$QZibCbKzc{75G^T7@WZSQ)j5898G1AOXB*H*TSd`f<`IK# zm1%&t?i|2Z-a&r!pJehzg@!awNp)R)aa?q_SqGrxE5u+T#f?K2;GAHV?O&>!W@Q*k)7=g2vDW+7K zbyY9i{|nOF*SbMYoRQSAbSH2y$bE5(@d6xKxcF#@TE~X#3o=;`0sc!RupdRmQsML? z&>SCwS{FOpSr+@6Uuz3m`hj}(^g`Jz|6?({!%WVJn$H|ugxW+x-GEA?J&U^ugj3Nb z;65~)W<}iH2PJ@st8LtLfSOLXYgj=9<;?ih7rq$bXW9J#!B8!Wu6#U`A$wlcoC*&` z_9Js~7%m79#+edeT&P`@_Ng@e&5J+pqpx%31tAF71)pcz~-yJ>P5yX(nuM4;bUHDa8E(~~l{j~JeCGkX>nHJDpgSf&bTHEf)qw8{Q~CBPEVen|MW2P3vmf`8X9-g|>>ddp zcgfjbl~(?3Wa*NzQH>4nsM$3}Ul>pX1xC0oF3TZXe7=V!9!n?WgvH|R zpbruczmB%z=zkZ>=1R|gXwGThLELqD5KCUhtiRGT*JwKIvzbzV%ZU!e!VcNHSSX3> zObH|oohc8nvQZ2}q??C}@>!fe3gH+HF@4(qWqi>;ag~md#D;cl8&gQb^?2a@5cikT z=7r78@&5gV3Ggc9f=<<8v~yz`NcEGvbX1V_`IL(&+Z>LB zM~$ok2qXzod@1$TEl*U~H$V5g$er{Uj^($sWb7Nr{gsIbE(`$LRGECTOraXiU%=uq z0zvpi1S%)RxTjzoVcR4#10)fs()4Mtsa@e?9j)Bk!LsYyXIZga2q7d%`vQE!V@<1Y zmkpH3LeXJNO9f7l>F84g;huc=4nk(UnU}RLZmYk2TtB#lv34K(?8~gyx-mN%g=U44 zOPdr_!j-;IEbe|l9-buuKEy^Q9MLjSKG$S6dz)!U_32{1)N}L)3+COmlg=nY1@od$ zJ<0z-B%sisAR1yh>z-RfQQb6M4i-d#vxvb~f69M{JLPZv1JSCh1$gQ*LxOF-tH9!k zbQ0ZW)S7)qCSF|=2`q_A3}OHBNBueZwTTz^ar~gz#2KA74&&D)KHt~m4F_nK<^*7_ z!!pN@xiGkq%>1N(rNxw$zu-=1t*IpAy$ z4~dD0w%9;E?(greVWZ3(o9ux`elM>Rek#0 zO=#-(4p5B+wFzlEU7^k{3EdL6sIp|K*>xrriI`}E8ze|z-$YpN`^_teL_7P`%e>IN z7tNiH619P+0Q1hBR|W#POOta)1|LkIRtgz zMJ9VOxXN#o)mlXS=u%`Q>~PBuKEmOWsIuQRp{y%!ty{fEyL0gV)$LQeL#pqX3L@SR zJ2Gb^E9+KVd?;joVOXlGie3?z6>(>u(i!(qGz(W( ze~^xj&IRF<98ypEis{Y_FoHn%C0bW(XeF#Lj=2WUEBqKNPPFppEH?_a3}-h906X}C zSYKcZFU`Om5YlWhh@ogzCn3NvuM~F9jOX|xe-X*!YL+#ceh_tJoHXz`aTnvSrOAZ| zOtdGz?QdT!oAJr3(XL2G(p%2X4{xEohU&vd_zQ(U%ihHOlKPWnb$&YYhx48?|R++>`5?sxvM?!;ru|9 zZ#nwuTK^S%ce<+ggdJBE&fRrXN7O!{nu`%q`M{2Ef_+IRad2cf01P9pST9AOK>y75c!9}~)Et^6$`&Nm{wzWcm4c0j9DF!xJTpGrMp3esI4D_iiDe`sswXSu{dQZE_`^A11 z?Z@Hw=65mVu^%X`>;$mciK}XiZ{xw7I_!t)S00^JuxdCXhIRO~S*lPS(S^je`DH4E zxbKNs8RL`N?gCQ@YSOU=>0FE#Ku#DRO7JA&fu-X8b;3!^#{=7`WsDXUxfUsE(FKSQ z&=N`A7IwLq%+vt(F;z+T=uZNl=@K4|E%p{p^o5(BGjsE|WOR`%8+XgGW8xJTFJc4L zVY#L`OdnSM{HyS$fX1)3_JuNNH1aDsDqi>CzCT5=kY5zV<~29bX)c^I8R5n&ymHkx zj(QC4t#mDK;2xi8O%V;C{HqDQeM64=b4@sa*N_K0a&ro4+8LY6cFHz< ze|!g}zF|tDrP=`+U7KwKl20gdW1%!iN>1=uxA|NZJ2peruBOj?RBPb~8G;s6xIi6- z?_odhafsxoxiBf zwZZ)c*)FLc0#wE~bXw0TPBYl+h9hs|DYr_B4LR_YL@S1hQs=p zNEh%_fUvWZCbJtaF#kP5=(O#{8|g&Kmz1&8{@Lufw^DhtvKx955~aqxi2C=)Z-!Kd z+m-u+#^U4(HYn6a1w652kO0bYBt&goyx(n?MR^kI+{Q?0Y{G~W2) z0dS3fuJ?SU(6ZDp=kUley%PK}K_;YQyK|U|?7t9SHiyIfpT4a_kUVIhH4PSaj@3mo z`z}|mHhx1Pq?@(3vTBb5HTXuFAzFZEt0D-fw_kd=XvwIUh3VXTm{wbDA~cESd5cI1 zd>6=&AvG3yu+)`9oxmfrDQ(1fzv(_0l?bp{a364dXLRRBI8kBv!KsL;brY)#E3`o{ z3TlWUsS0{Voci?6MejccG9x_KiqN>So*1{25r6BSl9jUyR}1TgXBLL7Pr6Wv~Nu47;fbiU7TbL}>qmtl36YSZ() zVf@nqW(As~#`@bIC+AxSw!O5Pocf&rYaCFm?Jd?XR)p#@{!|5^Ws@wd855)mI^8y{ zws+VvGXW6%xoj@JkGb=~%oJ~7m6+uhOv?bH+jJJ~eFgp+}~*^C+3>R-MY!IZQoabCh( zN(T+z@Oyc^C)WqQESmh{d!!T8zS(!wX=R#hEKxMXy(eg zZ+Cwm1a%?;RH$h2_ws|nRjn8ZY!>3gn+6Ep4xT|AeFox7!rac2Lw?jsz}JqPE?5JG zok0}q1P;cuzs%Yrze|&d$oTr<`Lx{fbq2OV=!3v-ODq(n?|WxuhtmwJBIoW^^FB+D z-?Ok9HBKc5@)L(W&vmI{prL?4^OE9TR)bELS=<>*w%&aKjzi*@;5#P3moG@dm{Eke zhE#Is;&=o|{2GWai}7LYEI+gmc^Kj4K7w7n)+9godg?yB2?xs}pF1<*!Sv?D~Uvbkgs9xx9s#6zBv9l@ox>d#H6eqw^KZO;Vg}h!q zI33^$4}yF*q+q{DsJsa(SsV!YQ#zi^IF9MQV6i{SiN4dWWCi%YQ+hNc1r!^+<(YnB zG62-D`M3w3Q2;@X{S`n`{QO>migDpz0FK`->sYDOESs6u>-~<}_XN_6><2g7U#XC{ z$#Ig;n{_yEMnlvx-lP*;ts#DHV0r8j518>~33?Ak#jocW>uk>6V||p7{4rov#RS9c zdPD6r`qF1om9r!zS4Jk1>7fn#GCnmD=JIt1Na`X)=*LP7R!3XATgk`;&U*P<(0d z9p<0T&eYqQ9jot39FxpfuPSPYlfQ$s-*;+c1KL+cHIVcG5`H~^Ryu1Hk7%Nf$TCwR!SzG31@NHpm`mcp8v!wyWM49TjTxASJ-8JP*MTHLC}hF==PUOh8kaaXeGFGd<|e29vSDaS ztPeu&zv0^wN}Hahi`$pcDs~FVt2F;K!q}q*Y@{7i#stWfU`u2La4aerBKhV`^zG~j zJWvtZpcHIP7x*tfLSQcng6D(`HVp4=LWp_0Xt=2wEHjK)!DSz_Z?5J@>awRyk?azj zU-kdSs~cp))*pfJ_q7u`IsCq8F|OShB~D56S(Mwwlt?{yURE7#eI&WcpVq(@9Fd~g zeUiD!a4w51Nj(YzLnau+O3MDub|?loF0=<#jLztAM>PruE7yNDD0L}y=Ayuc?^?Ni zf~%GK=iEhn2}xKp7GonJx!JpDmDsco$|$XtRdUDwbM9$9s7x9-of2nKNj~?b@UOKz z9{`=Irz^ba-c&1vSQxSh;I2`cKc8-4)aCy%#bam;3_8vSJ-jw`_}lyukEC~z00EbC zI*dU3F21A)dSZr{qA5QF+{a%D`h#?8o%M?)*hWxuqnQD(TpcmfNq&UN$BmB)0!r8) zxno@Q?$_D&*4(rW6b+?-Y^5|*P`DHmJ%pI<6*yP)o}2^?>d7P#bd2j=vvx2mfLW@R zQLD`%buR*}nzNYNf%68w-D$7%v|=bXg1mYrdZy~}(@RRZ-U+Gx=nmCjVxr5Ag# zLw3R29-MHJl|`mRxj#sv@EfyR#-q>BE-XFEENbV$#dWM?!VjU8~kKZsd@G=HPrI{HiqN&j<92*-3$^M*;n@rG*i! zvi#?j;lc5w>@+r!6*CVUrN9as=S3?(ZBT979$5R#ZpPm?2VjIyQcEFp9orGR>f;G? zK<~FiYY6ow-&}|v7k?+03TC++so$)2~rN``u z>N%j$AbNQLX_!evzG8abf=15260vIXdz7K^a$YS)iw{@x5<|Rr#ii|ov=LJ{eu>dZYe_ip$ZuzvRu1dpjQK1BvP zH~m#t=2_wy>9+YkdNF-z` zQ*#7=^r%R*pIi2AI`>n9>(QJVE1k8?Ilav<)NUjW^O$}^yZZ{_Uwn!4Fq1`aslX;Y zj`XDIm`E1sz|wShA=?a@ZGKDSMU#Z3$E!1nZ)g^Eg3ZDoSN6@RXrGVCHvMIauS7d> zuJltXf9)LdTWdF!n%-iA9b#2$W#i??K)zYho^((ZqluvhAr@{H{diy0%@-~VW zKYC|2Ma)2^=skdLT@ZVqJfiCDqS@~qIGexL(BKy6Aw9ch0hoHN&E+m3*uka9+AIh3gTWdSe~W({-&^oFw`!j7$DcsF$7`pO?kRMK<9h=SV?cmyJIe`$4|zoI(6u9#qY9zM?#zNe^!Dl2>Z^dH`>`wSY# ztU;V*+g0R0DH6EnJA$U{QL&T~&s{`smeC2I-5mzv=v$l@iF;yN0hMibU=CG^e>J;+9k`Si9PzLaj$>}QKI6lWmO_o+_( zmhxA*0|-Na`+*J1qEMIXZf9rb#;pcOw>EDeDjb!|GumQ2!1ac;YqU|X;F@l1_lemzTN0J|U zFJF(kO21aHg)*KfuKT=BA{VDkOvlx(b{f|A9D69_BHUm#S$F>~`Mt@GesjLp3;reY zP~q>6Tt;`XkjqV?i7lqPbWGh`y<7dq<}pDHl-dDA4QG6`QDq)+vq_&HfW!}P6Cp4d zt>Qnli5ri*I1ILEOGD~3Y!@2^Jmcy1xDXmKolC?at}_6;neEfca0rLHT}NLpoUYh` zDbCtfZnYN&>}m-(F{5d1=)bBuZ?OcP`GmsQV@kn%JMJUIep`Avon#8=ATpEo-@hg& z12f-)R=HCD%pUjvbWa|P!}u)=wInpZG*LHKrZDMeC>Qils^IyY)x;kDRs4c3!DDOG zAptSsf#1X>kSli|Qka@S)6O4un-2aKL?bcV;$*>KSxHovjrfZ^-+c#>;(42yj71K| zzRyFiLrwv$rPcNA{mtv=o(*JDA0kS93>OE0D{KMJzLk$cc_5dCLWnJcFJd6_>BpE< z?aW9;^!;arQcIjloW&YL+~MkNO&a>N=pmhg>{SM<@`a&VeUA`ay*P@R$_+WS2%r?_ zs&Z%c`>ie+%!I=Lz>$9$7a`-`hoc&*dl60^whsaQ;~9~@JYn1Oc_bmgVVyAzUOYgZ z#j{`#D_YZ)(wa5;qzR#zo4a|-ANJjBB90r4Iun3*BkMxw_Ti>SjhktsmR|BPCLt>9 zZ_3eQjweI*-8+HNt)$9^s|+10w@sU!PY{`#BnF!ULS=#{k0Zr5`yOS?p8PfWbKT`6 z@T+PeRJ4`fj5t8bMs)0>o9|C>mBTlfQ*nFG#Rri-Q7}E}+eaz`LmO!`Y_pHkoAruu z`&!5VNnA3IG$}Pz)V&pt&AF!$E{J-;or3vWv3&Sl&9KzG+ae73Zf}=aP*SCI1{?0T z9SAC)W(?DSKOkcmW$(K5Bl?c@(5#>J#j@eq#ctX~$TIjkl>Wrfv%Ey+bl1Z-v?NxJ zwZ9!ae-MsHPUx&_W22?9$mCE%&~lzVG?hDXM%~gXGk+Q!Jf0BspkMWxy;^!n<6JIrSYjv z6F%~$8)0^qbUho9Sdf97b_n({$;|XH9-RHrohHuPcro@03KEPFejN&q?&nJFoIQY; zSI#uL6>2^^yOR!51OLO65xGas55dPG;3=uQ35ZYW04#+~byXQf^7Vq`G z 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 Pe7HEdAEg|=c=LY&YVNkY literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..13b35eba55c6dabc3aac36f33d859266c18fa0d0 GIT binary patch literal 5680 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 literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3f5fa40fb3d1e0710331a48de5d256da3f275d GIT binary patch literal 520 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K#jR^;j87-Auq zoUlN^K{r-Q+XN;zI ze|?*NFmgt#V#GwrSWaz^2G&@SBmck6ZcIFMww~vE<1E?M2#KUn1CzsB6D2+0SuRV@ zV2kK5HvIGB{HX-hQzs0*AB%5$9RJ@a;)Ahq#p$GSP91^&hi#6sg*;a~dt}4AclK>h z_3MoPRQ{i;==;*1S-mY<(JFzhAxMI&<61&m$J0NDHdJ3tYx~j0%M-uN6Zl8~_0DOkGXc0001@sz3l12C6Xg{AT~( zm6w64BA|AX`Ve)YY-glyudNN>MAfkXz-T7`_`fEolM;0T0BA)(02-OaW z0*cW7Z~ec94o8&g0D$N>b!COu{=m}^%oXZ4?T8ZyPZuGGBPBA7pbQMoV5HYhiT?%! zcae~`(QAN4&}-=#2f5fkn!SWGWmSeCISBcS=1-U|MEoKq=k?_x3apK>9((R zuu$9X?^8?@(a{qMS%J8SJPq))v}Q-ZyDm6Gbie0m92=`YlwnQPQP1kGSm(N2UJ3P6 z^{p-u)SSCTW~c1rw;cM)-uL2{->wCn2{#%;AtCQ!m%AakVs1K#v@(*-6QavyY&v&*wO_rCJXJuq$c$7ZjsW+pJo-$L^@!7X04CvaOpPyfw|FKvu;e(&Iw>Tbg zL}#8e^?X%TReXTt>gsBByt0kSU20oQx*~P=4`&tcZ7N6t-6LiK{LxX*p6}9c<0Pu^ zLx1w_P4P2V>bX=`F%v$#{sUDdF|;rbI{p#ZW`00Bgh(eB(nOIhy8W9T>3aQ=k8Z9% zB+TusFABF~J?N~fAd}1Rme=@4+1=M{^P`~se7}e3;mY0!%#MJf!XSrUC{0uZqMAd7%q zQY#$A>q}noIB4g54Ue)x>ofVm3DKBbUmS4Z-bm7KdKsUixva)1*&z5rgAG2gxG+_x zqT-KNY4g7eM!?>==;uD9Y4iI(Hu$pl8!LrK_Zb}5nv(XKW{9R144E!cFf36p{i|8pRL~p`_^iNo z{mf7y`#hejw#^#7oKPlN_Td{psNpNnM?{7{R-ICBtYxk>?3}OTH_8WkfaTLw)ZRTfxjW+0>gMe zpKg~`Bc$Y>^VX;ks^J0oKhB#6Ukt{oQhN+o2FKGZx}~j`cQB%vVsMFnm~R_1Y&Ml? zwFfb~d|dW~UktY@?zkau>Owe zRroi(<)c4Ux&wJfY=3I=vg)uh;sL(IYY9r$WK1$F;jYqq1>xT{LCkIMb3t2jN8d`9 z=4(v-z7vHucc_fjkpS}mGC{ND+J-hc_0Ix4kT^~{-2n|;Jmn|Xf9wGudDk7bi*?^+ z7fku8z*mbkGm&xf&lmu#=b5mp{X(AwtLTf!N`7FmOmX=4xwbD=fEo8CaB1d1=$|)+ z+Dlf^GzGOdlqTO8EwO?8;r+b;gkaF^$;+#~2_YYVH!hD6r;PaWdm#V=BJ1gH9ZK_9 zrAiIC-)z)hRq6i5+$JVmR!m4P>3yJ%lH)O&wtCyum3A*})*fHODD2nq!1@M>t@Za+ zH6{(Vf>_7!I-APmpsGLYpl7jww@s5hHOj5LCQXh)YAp+y{gG(0UMm(Ur z3o3n36oFwCkn+H*GZ-c6$Y!5r3z*@z0`NrB2C^q#LkOuooUM8Oek2KBk}o1PU8&2L z4iNkb5CqJWs58aR394iCU^ImDqV;q_Pp?pl=RB2372(Io^GA^+oKguO1(x$0<7w3z z)j{vnqEB679Rz4i4t;8|&Zg77UrklxY9@GDq(ZphH6=sW`;@uIt5B?7Oi?A0-BL}(#1&R;>2aFdq+E{jsvpNHjLx2t{@g1}c~DQcPNmVmy| zNMO@ewD^+T!|!DCOf}s9dLJU}(KZy@Jc&2Nq3^;vHTs}Hgcp`cw&gd7#N}nAFe3cM1TF%vKbKSffd&~FG9y$gLyr{#to)nxz5cCASEzQ}gz8O)phtHuKOW6p z@EQF(R>j%~P63Wfosrz8p(F=D|Mff~chUGn(<=CQbSiZ{t!e zeDU-pPsLgtc#d`3PYr$i*AaT!zF#23htIG&?QfcUk+@k$LZI}v+js|yuGmE!PvAV3 ztzh90rK-0L6P}s?1QH`Ot@ilbgMBzWIs zIs6K<_NL$O4lwR%zH4oJ+}JJp-bL6~%k&p)NGDMNZX7)0kni&%^sH|T?A)`z z=adV?!qnWx^B$|LD3BaA(G=ePL1+}8iu^SnnD;VE1@VLHMVdSN9$d)R(Wk{JEOp(P zm3LtAL$b^*JsQ0W&eLaoYag~=fRRdI>#FaELCO7L>zXe6w*nxN$Iy*Q*ftHUX0+N- zU>{D_;RRVPbQ?U+$^%{lhOMKyE5>$?U1aEPist+r)b47_LehJGTu>TcgZe&J{ z{q&D{^Ps~z7|zj~rpoh2I_{gAYNoCIJmio3B}$!5vTF*h$Q*vFj~qbo%bJCCRy509 zHTdDh_HYH8Zb9`}D5;;J9fkWOQi%Y$B1!b9+ESj+B@dtAztlY2O3NE<6HFiqOF&p_ zW-K`KiY@RPSY-p9Q99}Hcd05DT79_pfb{BV7r~?9pWh=;mcKBLTen%THFPo2NN~Nf zriOtFnqx}rtO|A6k!r6 zf-z?y-UD{dT0kT9FJ`-oWuPHbo+3wBS(}?2ql(+e@VTExmfnB*liCb zmeI+v5*+W_L;&kQN^ChW{jE0Mw#0Tfs}`9bk3&7UjxP^Ke(%eJu2{VnW?tu7Iqecm zB5|=-QdzK$=h50~{X3*w4%o1FS_u(dG2s&427$lJ?6bkLet}yYXCy)u_Io1&g^c#( z-$yYmSpxz{>BL;~c+~sxJIe1$7eZI_9t`eB^Pr0)5CuA}w;;7#RvPq|H6!byRzIJG ziQ7a4y_vhj(AL`8PhIm9edCv|%TX#f50lt8+&V+D4<}IA@S@#f4xId80oH$!_!q?@ zFRGGg2mTv&@76P7aTI{)Hu%>3QS_d)pQ%g8BYi58K~m-Ov^7r8BhX7YC1D3vwz&N8{?H*_U7DI?CI)+et?q|eGu>42NJ?K4SY zD?kc>h@%4IqNYuQ8m10+8xr2HYg2qFNdJl=Tmp&ybF>1>pqVfa%SsV*BY$d6<@iJA ziyvKnZ(~F9xQNokBgMci#pnZ}Igh0@S~cYcU_2Jfuf|d3tuH?ZSSYBfM(Y3-JBsC|S9c;# zyIMkPxgrq};0T09pjj#X?W^TFCMf1-9P{)g88;NDI+S4DXe>7d3Mb~i-h&S|Jy{J< zq3736$bH?@{!amD!1Ys-X)9V=#Z={fzsjVYMX5BG6%}tkzwC#1nQLj1y1f#}8**4Y zAvDZHw8)N)8~oWC88CgzbwOrL9HFbk4}h85^ptuu7A+uc#$f^9`EWv1Vr{5+@~@Uv z#B<;-nt;)!k|fRIg;2DZ(A2M2aC65kOIov|?Mhi1Sl7YOU4c$T(DoRQIGY`ycfkn% zViHzL;E*A{`&L?GP06Foa38+QNGA zw3+Wqs(@q+H{XLJbwZzE(omw%9~LPZfYB|NF5%j%E5kr_xE0u;i?IOIchn~VjeDZ) zAqsqhP0vu2&Tbz3IgJvMpKbThC-@=nk)!|?MIPP>MggZg{cUcKsP8|N#cG5 zUXMXxcXBF9`p>09IR?x$Ry3;q@x*%}G#lnB1}r#!WL88I@uvm}X98cZ8KO&cqT1p> z+gT=IxPsq%n4GWgh-Bk8E4!~`r@t>DaQKsjDqYc&h$p~TCh8_Mck5UB84u6Jl@kUZCU9BA-S!*bf>ZotFX9?a_^y%)yH~rsAz0M5#^Di80_tgoKw(egN z`)#(MqAI&A84J#Z<|4`Co8`iY+Cv&iboMJ^f9ROUK0Lm$;-T*c;TCTED_0|qfhlcS zv;BD*$Zko#nWPL}2K8T-?4}p{u)4xon!v_(yVW8VMpxg4Kh^J6WM{IlD{s?%XRT8P|yCU`R&6gwB~ zg}{At!iWCzOH37!ytcPeC`(({ovP7M5Y@bYYMZ}P2Z3=Y_hT)4DRk}wfeIo%q*M9UvXYJq!-@Ly79m5aLD{hf@BzQB>FdQ4mw z6$@vzSKF^Gnzc9vbccii)==~9H#KW<6)Uy1wb~auBn6s`ct!ZEos`WK8e2%<00b%# zY9Nvnmj@V^K(a_38dw-S*;G-(i(ETuIwyirs?$FFW@|66a38k+a%GLmucL%Wc8qk3 z?h_4!?4Y-xt)ry)>J`SuY**fuq2>u+)VZ+_1Egzctb*xJ6+7q`K$^f~r|!i?(07CD zH!)C_uerf-AHNa?6Y61D_MjGu*|wcO+ZMOo4q2bWpvjEWK9yASk%)QhwZS%N2_F4& z16D18>e%Q1mZb`R;vW{+IUoKE`y3(7p zplg5cBB)dtf^SdLd4n60oWie|(ZjgZa6L*VKq02Aij+?Qfr#1z#fwh92aV-HGd^_w zsucG24j8b|pk>BO7k8dS86>f-jBP^Sa}SF{YNn=^NU9mLOdKcAstv&GV>r zLxKHPkFxpvE8^r@MSF6UA}cG`#yFL8;kA7ccH9D=BGBtW2;H>C`FjnF^P}(G{wU;G z!LXLCbPfsGeLCQ{Ep$^~)@?v`q(uI`CxBY44osPcq@(rR-633!qa zsyb>?v%@X+e|Mg`+kRL*(;X>^BNZz{_kw5+K;w?#pReiw7eU8_Z^hhJ&fj80XQkuU z39?-z)6Fy$I`bEiMheS(iB6uLmiMd1i)cbK*9iPpl+h4x9ch7x- z1h4H;W_G?|)i`z??KNJVwgfuAM=7&Apd3vm#AT8uzQZ!NII}}@!j)eIfn53h{NmN7 zAKG6SnKP%^k&R~m5#@_4B@V?hYyHkm>0SQ@PPiw*@Tp@UhP-?w@jW?nxXuCipMW=L zH*5l*d@+jXm0tIMP_ec6Jcy6$w(gKK@xBX8@%oPaSyG;13qkFb*LuVx3{AgIyy&n3 z@R2_DcEn|75_?-v5_o~%xEt~ONB>M~tpL!nOVBLPN&e5bn5>+7o0?Nm|EGJ5 zmUbF{u|Qn?cu5}n4@9}g(G1JxtzkKv(tqwm_?1`?YSVA2IS4WI+*(2D*wh&6MIEhw z+B+2U<&E&|YA=3>?^i6)@n1&&;WGHF-pqi_sN&^C9xoxME5UgorQ_hh1__zzR#zVC zOQt4q6>ME^iPJ37*(kg4^=EFqyKH@6HEHXy79oLj{vFqZGY?sVjk!BX^h$SFJlJnv z5uw~2jLpA)|0=tp>qG*tuLru?-u`khGG2)o{+iDx&nC}eWj3^zx|T`xn5SuR;Aw8U z`p&>dJw`F17@J8YAuW4=;leBE%qagVTG5SZdh&d)(#ZhowZ|cvWvGMMrfVsbg>_~! z19fRz8CSJdrD|Rl)w!uznBF&2-dg{>y4l+6(L(vzbLA0Bk&`=;oQQ>(M8G=3kto_) zP8HD*n4?MySO2YrG6fwSrVmnesW+D&fxjfEmp=tPd?RKLZJcH&K(-S+x)2~QZ$c(> zru?MND7_HPZJVF%wX(49H)+~!7*!I8w72v&{b={#l9yz+S_aVPc_So%iF8>$XD1q1 zFtucO=rBj0Ctmi0{njN8l@}!LX}@dwl>3yMxZ;7 z0Ff2oh8L)YuaAGOuZ5`-p%Z4H@H$;_XRJQ|&(MhO78E|nyFa158gAxG^SP(vGi^+< zChY}o(_=ci3Wta#|K6MVljNe0T$%Q5ylx-v`R)r8;3+VUpp-)7T`-Y&{Zk z*)1*2MW+_eOJtF5tCMDV`}jg-R(_IzeE9|MBKl;a7&(pCLz}5<Zf+)T7bgNUQ_!gZtMlw=8doE}#W+`Xp~1DlE=d5SPT?ymu!r4z%&#A-@x^=QfvDkfx5-jz+h zoZ1OK)2|}_+UI)i9%8sJ9X<7AA?g&_Wd7g#rttHZE;J*7!e5B^zdb%jBj&dUDg4&B zMMYrJ$Z%t!5z6=pMGuO-VF~2dwjoXY+kvR>`N7UYfIBMZGP|C7*O=tU z2Tg_xi#Q3S=1|=WRfZD;HT<1D?GMR%5kI^KWwGrC@P2@R>mDT^3qsmbBiJc21kip~ zZp<7;^w{R;JqZ)C4z-^wL=&dBYj9WJBh&rd^A^n@07qM$c+kGv^f+~mU5_*|eePF| z3wDo-qaoRjmIw<2DjMTG4$HP{z54_te_{W^gu8$r=q0JgowzgQPct2JNtWPUsjF8R zvit&V8$(;7a_m%%9TqPkCXYUp&k*MRcwr*24>hR! z$4c#E=PVE=P4MLTUBM z7#*RDe0}=B)(3cvNpOmWa*eH#2HR?NVqXdJ=hq);MGD07JIQQ7Y0#iD!$C+mk7x&B zMwkS@H%>|fmSu#+ zI!}Sb(%o29Vkp_Th>&&!k7O>Ba#Om~B_J{pT7BHHd8(Ede(l`7O#`_}19hr_?~JP9 z`q(`<)y>%)x;O7)#-wfCP{?llFMoH!)ZomgsOYFvZ1DxrlYhkWRw#E-#Qf*z@Y-EQ z1~?_=c@M4DO@8AzZ2hKvw8CgitzI9yFd&N1-{|vP#4IqYb*#S0e3hrjsEGlnc4xwk z4o!0rxpUt8j&`mJ8?+P8G{m^jbk)bo_UPM+ifW*y-A*et`#_Ja_3nYyRa9fAG1Xr5 z>#AM_@PY|*u)DGRWJihZvgEh#{*joJN28uN7;i5{kJ*Gb-TERfN{ERe_~$Es~NJCpdKLRvdj4658uYYx{ng7I<6j~w@p%F<7a(Ssib|j z51;=Py(Nu*#hnLx@w&8X%=jrADn3TW>kplnb zYbFIWWVQXN7%Cwn6KnR)kYePEBmvM45I)UJb$)ninpdYg3a5N6pm_7Q+9>!_^xy?k za8@tJ@OOs-pRAAfT>Nc2x=>sZUs2!9Dwa%TTmDggH4fq(x^MW>mcRyJINlAqK$YQCMgR8`>6=Sg$ zFnJZsA8xUBXIN3i70Q%8px@yQPMgVP=>xcPI38jNJK<=6hC={a07+n@R|$bnhB)X$ z(Zc%tadp70vBTnW{OUIjTMe38F}JIH$#A}PB&RosPyFZMD}q}5W%$rh>5#U;m`z2K zc(&WRxx7DQLM-+--^w*EWAIS%bi>h587qkwu|H=hma3T^bGD&Z!`u(RKLeNZ&pI=q$|HOcji(0P1QC!YkAp*u z3%S$kumxR}jU<@6`;*-9=5-&LYRA<~uFrwO3U0k*4|xUTp4ZY7;Zbjx|uw&BWU$zK(w55pWa~#=f$c zNDW0O68N!xCy>G}(CX=;8hJLxAKn@Aj(dbZxO8a$+L$jK8$N-h@4$i8)WqD_%Snh4 zR?{O%k}>lr>w$b$g=VP8mckcCrjnp>uQl5F_6dPM8FWRqs}h`DpfCv20uZhyY~tr8 zkAYW4#yM;*je)n=EAb(q@5BWD8b1_--m$Q-3wbh1hM{8ihq7UUQfg@)l06}y+#=$( z$x>oVYJ47zAC^>HLRE-!HitjUixP6!R98WU+h>zct7g4eD;Mj#FL*a!VW!v-@b(Jv zj@@xM5noCp5%Vk3vY{tyI#oyDV7<$`KG`tktVyC&0DqxA#>V;-3oH%NW|Q&=UQ&zU zXNIT67J4D%5R1k#bW0F}TD`hlW7b)-=-%X4;UxQ*u4bK$mTAp%y&-(?{sXF%e_VH6 zTkt(X)SSN|;8q@8XX6qfR;*$r#HbIrvOj*-5ND8RCrcw4u8D$LXm5zlj@E5<3S0R# z??=E$p{tOk96$SloZ~ARe5`J=dB|Nj?u|zy2r(-*(q^@YwZiTF@QzQyPx_l=IDKa) zqD@0?IHJqSqZ_5`)81?4^~`yiGh6>7?|dKa8!e|}5@&qV!Iu9<@G?E}Vx9EzomB3t zEbMEm$TKGwkHDpirp;FZD#6P5qIlQJ8}rf;lHoz#h4TFFPYmS3+8(13_Mx2`?^=8S z|0)0&dQLJTU6{b%*yrpQe#OKKCrL8}YKw+<#|m`SkgeoN69TzIBQOl_Yg)W*w?NW) z*WxhEp$zQBBazJSE6ygu@O^!@Fr46j=|K`Mmb~xbggw7<)BuC@cT@Bwb^k?o-A zKX^9AyqR?zBtW5UA#siILztgOp?r4qgC`9jYJG_fxlsVSugGprremg-W(K0{O!Nw-DN%=FYCyfYA3&p*K>+|Q}s4rx#CQK zNj^U;sLM#q8}#|PeC$p&jAjqMu(lkp-_50Y&n=qF9`a3`Pr9f;b`-~YZ+Bb0r~c+V z*JJ&|^T{}IHkwjNAaM^V*IQ;rk^hnnA@~?YL}7~^St}XfHf6OMMCd9!vhk#gRA*{L zp?&63axj|Si%^NW05#87zpU_>QpFNb+I00v@cHwvdBn+Un)n2Egdt~LcWOeBW4Okm zD$-e~RD+W|UB;KQ;a7GOU&%p*efGu2$@wR74+&iP8|6#_fmnh^WcJLs)rtz{46);F z4v0OL{ZP9550>2%FE(;SbM*#sqMl*UXOb>ch`fJ|(*bOZ9=EB1+V4fkQ)hjsm3-u^Pk-4ji_uDDHdD>84tER!MvbH`*tG zzvbhBR@}Yd`azQGavooV=<WbvWLlO#x`hyO34mKcxrGv=`{ssnP=0Be5#1B;Co9 zh{TR>tjW2Ny$ZxJpYeg57#0`GP#jxDCU0!H15nL@@G*HLQcRdcsUO3sO9xvtmUcc{F*>FQZcZ5bgwaS^k-j5mmt zI7Z{Xnoml|A(&_{imAjK!kf5>g(oDqDI4C{;Bv162k8sFNr;!qPa2LPh>=1n z=^_9)TsLDvTqK7&*Vfm5k;VXjBW^qN3Tl&}K=X5)oXJs$z3gk0_+7`mJvz{pK|FVs zHw!k&7xVjvY;|(Py<;J{)b#Yjj*LZO7x|~pO4^MJ2LqK3X;Irb%nf}L|gck zE#55_BNsy6m+W{e zo!P59DDo*s@VIi+S|v93PwY6d?CE=S&!JLXwE9{i)DMO*_X90;n2*mPDrL%{iqN!?%-_95J^L z=l<*{em(6|h7DR4+4G3Wr;4*}yrBkbe3}=p7sOW1xj!EZVKSMSd;QPw>uhKK z#>MlS@RB@-`ULv|#zI5GytO{=zp*R__uK~R6&p$q{Y{iNkg61yAgB8C^oy&``{~FK z8hE}H&nIihSozKrOONe5Hu?0Zy04U#0$fB7C6y~?8{or}KNvP)an=QP&W80mj&8WL zEZQF&*FhoMMG6tOjeiCIV;T{I>jhi9hiUwz?bkX3NS-k5eWKy)Mo_orMEg4sV6R6X&i-Q%JG;Esl+kLpn@Bsls9O|i9z`tKB^~1D5)RIBB&J<6T@a4$pUvh$IR$%ubH)joi z!7>ON0DPwx=>0DA>Bb^c?L8N0BBrMl#oDB+GOXJh;Y&6I)#GRy$W5xK%a;KS8BrER zX)M>Rdoc*bqP*L9DDA3lF%U8Yzb6RyIsW@}IKq^i7v&{LeIc=*ZHIbO68x=d=+0T( zev=DT9f|x!IWZNTB#N7}V4;9#V$%Wo0%g>*!MdLOEU>My0^gni9ocID{$g9ytD!gy zKRWT`DVN(lcYjR|(}f0?zgBa3SwunLfAhx><%u0uFkrdyqlh8_g zDKt#R6rA2(Vm2LW_>3lBNYKG_F{TEnnKWGGC15y&OebIRhFL4TeMR*v9i0wPoK#H< zu4){s4K&K)K(9~jgGm;H7lS7y_RYfS;&!Oj5*eqbvEcW^a*i67nevzOZxN6F+K~A%TYEtsAVsR z@J=1hc#Dgs7J2^FL|qV&#WBFQyDtEQ2kPO7m2`)WFhqAob)Y>@{crkil6w9VoA?M6 zADGq*#-hyEVhDG5MQj677XmcWY1_-UO40QEP&+D)rZoYv^1B_^w7zAvWGw&pQyCyx zD|ga$w!ODOxxGf_Qq%V9Z7Q2pFiUOIK818AGeZ-~*R zI1O|SSc=3Z?#61Rd|AXx2)K|F@Z1@x!hBBMhAqiU)J=U|Y)T$h3D?ZPPQgkSosnN! zIqw-t$0fqsOlgw3TlHJF*t$Q@bg$9}A3X=cS@-yU3_vNG_!#9}7=q7!LZ?-%U26W4 z$d>_}*s1>Ac%3uFR;tnl*fNlylJ)}r2^Q3&@+is3BIv<}x>-^_ng;jhdaM}6Sg3?p z0jS|b%QyScy3OQ(V*~l~bK>VC{9@FMuW_JUZO?y(V?LKWD6(MXzh}M3r3{7b4eB(#`(q1m{>Be%_<9jw8HO!x#yF6vez$c#kR+}s zZO-_;25Sxngd(}){zv?ccbLqRAlo;yog>4LH&uZUK1n>x?u49C)Y&2evH5Zgt~666 z_2_z|H5AO5Iqxv_Bn~*y1qzRPcob<+Otod5Xd2&z=C;u+F}zBB@b^UdGdUz|s!H}M zXG%KiLzn3G?FZgdY&3pV$nSeY?ZbU^jhLz9!t0K?ep}EFNqR1@E!f*n>x*!uO*~JF zW9UXWrVgbX1n#76_;&0S7z}(5n-bqnII}_iDsNqfmye@)kRk`w~1 z6j4h4BxcPe6}v)xGm%=z2#tB#^KwbgMTl2I*$9eY|EWAHFc3tO48Xo5rW z5oHD!G4kb?MdrOHV=A+8ThlIqL8Uu+7{G@ zb)cGBm|S^Eh5= z^E^SZ=yeC;6nNCdztw&TdnIz}^Of@Ke*@vjt)0g>Y!4AJvWiL~e7+9#Ibhe)> ziNwh>gWZL@FlWc)wzihocz+%+@*euwXhW%Hb>l7tf8aJe5_ZSH1w-uG|B;9qpcBP0 zM`r1Hu#htOl)4Cl1c7oY^t0e4Jh$-I(}M5kzWqh{F=g&IM#JiC`NDSd@BCKX#y<P@Gwl$3a3w z6<(b|K(X5FIR22M)sy$4jY*F4tT{?wZRI+KkZFb<@j@_C316lu1hq2hA|1wCmR+S@ zRN)YNNE{}i_H`_h&VUT5=Y(lN%m?%QX;6$*1P}K-PcPx>*S55v)qZ@r&Vcic-sjkm z! z=nfW&X`}iAqa_H$H%z3Tyz5&P3%+;93_0b;zxLs)t#B|up}JyV$W4~`8E@+BHQ+!y zuIo-jW!~)MN$2eHwyx-{fyGjAWJ(l8TZtUp?wZWBZ%}krT{f*^fqUh+ywHifw)_F> zp76_kj_B&zFmv$FsPm|L7%x-j!WP>_P6dHnUTv!9ZWrrmAUteBa`rT7$2ixO;ga8U z3!91micm}{!Btk+I%pMgcKs?H4`i+=w0@Ws-CS&n^=2hFTQ#QeOmSz6ttIkzmh^`A zYPq)G1l3h(E$mkyr{mvz*MP`x+PULBn%CDhltKkNo6Uqg!vJ#DA@BIYr9TQ`18Un2 zv$}BYzOQuay9}w(?JV63F$H6WmlYPPpH=R|CPb%C@BCv|&Q|&IcW7*LX?Q%epS z`=CPx{1HnJ9_46^=0VmNb>8JvMw-@&+V8SDLRYsa>hZXEeRbtf5eJ>0@Ds47zIY{N z42EOP9J8G@MXXdeiPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91AfN*P1ONa40RR91AOHXW0IY^$^8f$?lu1NER9Fe^SItioK@|V(ZWmgL zZT;XwPgVuWM>O%^|Dc$VK;n&?9!&g5)aVsG8cjs5UbtxVVnQNOV~7Mrg3+jnU;rhE z6fhW6P)R>_eXrXo-RW*y6RQ_qcb^s1wTu$TwriZ`=JUws>vRi}5x}MW1MR#7p|gIWJlaLK;~xaN}b< z<-@=RX-%1mt`^O0o^~2=CD7pJ<<$Rp-oUL-7PuG>do^5W_Mk#unlP}6I@6NPxY`Q} zuXJF}!0l)vwPNAW;@5DjPRj?*rZxl zwn;A(cFV!xe^CUu+6SrN?xe#mz?&%N9QHf~=KyK%DoB8HKC)=w=3E?1Bqj9RMJs3U z5am3Uv`@+{jgqO^f}Lx_Jp~CoP3N4AMZr~4&d)T`R?`(M{W5WWJV^z~2B|-oih@h^ zD#DuzGbl(P5>()u*YGo*Och=oRr~3P1wOlKqI)udc$|)(bacG5>~p(y>?{JD7nQf_ z*`T^YL06-O>T(s$bi5v~_fWMfnE7Vn%2*tqV|?~m;wSJEVGkNMD>+xCu#um(7}0so zSEu7?_=Q64Q5D+fz~T=Rr=G_!L*P|(-iOK*@X8r{-?oBlnxMNNgCVCN9Y~ocu+?XA zjjovJ9F1W$Nf!{AEv%W~8oahwM}4Ruc+SLs>_I_*uBxdcn1gQ^2F8a*vGjgAXYyh? zWCE@c5R=tbD(F4nL9NS?$PN1V_2*WR?gjv3)4MQeizuH`;sqrhgykEzj z593&TGlm3h`sIXy_U<7(dpRXGgp0TB{>s?}D{fwLe>IV~exweOfH!qM@CV5kib!YA z6O0gvJi_0J8IdEvyP#;PtqP*=;$iI2t(xG2YI-e!)~kaUn~b{6(&n zp)?iJ`z2)Xh%sCV@BkU`XL%_|FnCA?cVv@h*-FOZhY5erbGh)%Q!Av#fJM3Csc_g zC2I6x%$)80`Tkz#KRA!h1FzY`?0es3t!rKDT5EjPe6B=BLPr7s0GW!if;Ip^!AmGW zL;$`Vdre+|FA!I4r6)keFvAx3M#1`}ijBHDzy)3t0gwjl|qC2YB`SSxFKHr(oY#H$)x{L$LL zBdLKTlsOrmb>T0wd=&6l3+_Te>1!j0OU8%b%N342^opKmT)gni(wV($s(>V-fUv@0p8!f`=>PxC|9=nu ze{ToBBj8b<{PLfXV$h8YPgA~E!_sF9bl;QOF{o6t&JdsX?}rW!_&d`#wlB6T_h;Xf zl{4Tz5>qjF4kZgjO7ZiLPRz_~U@k5%?=30+nxEh9?s78gZ07YHB`FV`4%hlQlMJe@J`+e(qzy+h(9yY^ckv_* zb_E6o4p)ZaWfraIoB2)U7_@l(J0O%jm+Or>8}zSSTkM$ASG^w3F|I? z$+eHt7T~04(_WfKh27zqS$6* zzyy-ZyqvSIZ0!kkSvHknm_P*{5TKLQs8S6M=ONuKAUJWtpxbL#2(_huvY(v~Y%%#~ zYgsq$JbLLprKkV)32`liIT$KKEqs$iYxjFlHiRNvBhxbDg*3@Qefw4UM$>i${R5uB zhvTgmqQsKA{vrKN;TSJU2$f9q=y{$oH{<)woSeV>fkIz6D8@KB zf4M%v%f5U2?<8B(xn}xV+gWP?t&oiapJhJbfa;agtz-YM7=hrSuxl8lAc3GgFna#7 zNjX7;`d?oD`#AK+fQ=ZXqfIZFEk{ApzjJF0=yO~Yj{7oQfXl+6v!wNnoqwEvrs81a zGC?yXeSD2NV!ejp{LdZGEtd1TJ)3g{P6j#2jLR`cpo;YX}~_gU&Gd<+~SUJVh+$7S%`zLy^QqndN<_9 zrLwnXrLvW+ew9zX2)5qw7)zIYawgMrh`{_|(nx%u-ur1B7YcLp&WFa24gAuw~& zKJD3~^`Vp_SR$WGGBaMnttT)#fCc^+P$@UHIyBu+TRJWbcw4`CYL@SVGh!X&y%!x~ zaO*m-bTadEcEL6V6*{>irB8qT5Tqd54TC4`h`PVcd^AM6^Qf=GS->x%N70SY-u?qr>o2*OV7LQ=j)pQGv%4~z zz?X;qv*l$QSNjOuQZ>&WZs2^@G^Qas`T8iM{b19dS>DaXX~=jd4B2u`P;B}JjRBi# z_a@&Z5ev1-VphmKlZEZZd2-Lsw!+1S60YwW6@>+NQ=E5PZ+OUEXjgUaXL-E0fo(E* zsjQ{s>n33o#VZm0e%H{`KJi@2ghl8g>a~`?mFjw+$zlt|VJhSU@Y%0TWs>cnD&61fW4e0vFSaXZa4-c}U{4QR8U z;GV3^@(?Dk5uc@RT|+5C8-24->1snH6-?(nwXSnPcLn#X_}y3XS)MI_?zQ$ZAuyg+ z-pjqsw}|hg{$~f0FzmmbZzFC0He_*Vx|_uLc!Ffeb8#+@m#Z^AYcWcZF(^Os8&Z4g zG)y{$_pgrv#=_rV^D|Y<_b@ICleUv>c<0HzJDOsgJb#Rd-Vt@+EBDPyq7dUM9O{Yp zuGUrO?ma2wpuJuwl1M=*+tb|qx7Doj?!F-3Z>Dq_ihFP=d@_JO;vF{iu-6MWYn#=2 zRX6W=`Q`q-+q@Db|6_a1#8B|#%hskH82lS|9`im0UOJn?N#S;Y0$%xZw3*jR(1h5s z?-7D1tnIafviko>q6$UyqVDq1o@cwyCb*})l~x<@s$5D6N=-Uo1yc49p)xMzxwnuZ zHt!(hu-Ek;Fv4MyNTgbW%rPF*dB=;@r3YnrlFV{#-*gKS_qA(G-~TAlZ@Ti~Yxw;k za1EYyX_Up|`rpbZ0&Iv#$;eC|c0r4XGaQ-1mw@M_4p3vKIIpKs49a8Ns#ni)G314Z z8$Ei?AhiT5dQGWUYdCS|IC7r z=-8ol>V?u!n%F*J^^PZ(ONT&$Ph;r6X;pj|03HlDY6r~0g~X#zuzVU%a&!fs_f|m?qYvg^Z{y?9Qh7Rn?T*F%7lUtA6U&={HzhYEzA`knx1VH> z{tqv?p@I(&ObD5L4|YJV$QM>Nh-X3cx{I&!$FoPC_2iIEJfPk-$;4wz>adRu@n`_y z_R6aN|MDHdK;+IJmyw(hMoDCFCQ(6?hCAG5&7p{y->0Uckv# zvooVuu04$+pqof777ftk<#42@KQ((5DPcSMQyzGOJ{e9H$a9<2Qi_oHjl{#=FUL9d z+~0^2`tcvmp0hENwfHR`Ce|<1S@p;MNGInXCtHnrDPXCKmMTZQ{HVm_cZ>@?Wa6}O zHsJc7wE)mc@1OR2DWY%ZIPK1J2p6XDO$ar`$RXkbW}=@rFZ(t85AS>>U0!yt9f49^ zA9@pc0P#k;>+o5bJfx0t)Lq#v4`OcQn~av__dZ-RYOYu}F#pdsl31C^+Qgro}$q~5A<*c|kypzd} ziYGZ~?}5o`S5lw^B{O@laad9M_DuJle- z*9C7o=CJh#QL=V^sFlJ0c?BaB#4bV^T(DS6&Ne&DBM_3E$S^S13qC$7_Z?GYXTpR@wqr70wu$7+qvf-SEUa5mdHvFbu^7ew!Z1a^ zo}xKOuT*gtGws-a{Tx}{#(>G~Y_h&5P@Q8&p!{*s37^QX_Ibx<6XU*AtDOIvk|^{~ zPlS}&DM5$Ffyu-T&0|KS;Wnaqw{9DB&B3}vcO14wn;)O_e@2*9B&0I_ zZz{}CMxx`hv-XouY>^$Y@J(_INeM>lIQI@I>dBAqq1)}?Xmx(qRuX^i4IV%=MF306 z9g)i*79pP%_7Ex?m6ag-4Tlm=Z;?DQDyC-NpUIb#_^~V_tsL<~5<&;Gf2N+p?(msn zzUD~g>OoW@O}y0@Z;RN)wjam`CipmT&O7a|YljZqU=U86 zedayEdY)2F#BJ6xvmW8K&ffdS*0!%N<%RB!2~PAT4AD*$W7yzHbX#Eja9%3aD+Ah2 zf#T;XJW-GMxpE=d4Y>}jE=#U`IqgSoWcuvgaWQ9j1CKzG zDkoMDDT)B;Byl3R2PtC`ip=yGybfzmVNEx{xi_1|Cbqj>=FxQc{g`xj6fIfy`D8fA z##!-H_e6o0>6Su&$H2kQTujtbtyNFeKc}2=|4IfLTnye#@$Au7Kv4)dnA;-fz@D_8 z)>irG$)dkBY~zX zC!ZXLy*L3xr6cb70QqfN#Q>lFIc<>}>la4@3%7#>a1$PU&O^&VszpxLC%*!m-cO{B z-Y}rQr4$84(hvy#R69H{H zJ*O#uJh)TF6fbXy;fZkk%X=CjsTK}o5N1a`d7kgYYZLPxsHx%9*_XN8VWXEkVJZ%A z1A+5(B;0^{T4aPYr8%i@i32h)_)|q?9vws)r+=5u)1YNftF5mknwfd*%jXA2TeP}Z zQ!m?xJ3?9LpPM?_A3$hQ1QxNbR&}^m z!F999s?p^ak#C4NM_x2p9FoXWJ$>r?lJ)2bG)sX{gExgLA2s5RwHV!h6!C~d_H||J z>9{E{mEv{Z1z~65Vix@dqM4ZqiU|!)eWX$mwS5mLSufxbpBqqS!jShq1bmwCR6 z4uBri7ezMeS6ycaXPVu(i2up$L; zjpMtB`k~WaNrdgM_R=e#SN?Oa*u%nQy01?()h4A(jyfeNfx;5o+kX?maO4#1A^L}0 zYNyIh@QVXIFiS0*tE}2SWTrWNP3pH}1Vz1;E{@JbbgDFM-_Mky^7gH}LEhl~Ve5PexgbIyZ(IN%PqcaV@*_`ZFb=`EjspSz%5m2E34BVT)d=LGyHVz@-e%9Ova*{5@RD;7=Ebkc2GP%pIP^P7KzKapnh`UpH?@h z$RBpD*{b?vhohOKf-JG3?A|AX|2pQ?(>dwIbWhZ38GbTm4AImRNdv_&<99ySX;kJ| zo|5YgbHZC#HYgjBZrvGAT4NZYbp}qkVSa;C-LGsR26Co+i_HM&{awuO9l)Ml{G8zD zs$M8R`r+>PT#Rg!J(K6T4xHq7+tscU(}N$HY;Yz*cUObX7J7h0#u)S7b~t^Oj}TBF zuzsugnst;F#^1jm>22*AC$heublWtaQyM6RuaquFd8V#hJ60Z3j7@bAs&?dD#*>H0SJaDwp%U~27>zdtn+ z|8sZzklZy$%S|+^ie&P6++>zbrq&?+{Yy11Y>@_ce@vU4ZulS@6yziG6;iu3Iu`M= zf3rcWG<+3F`K|*(`0mE<$89F@jSq;j=W#E>(R}2drCB7D*0-|D;S;(;TwzIJkGs|q z2qH{m_zZ+el`b;Bv-#bQ>}*VPYC|7`rgBFf2oivXS^>v<&HHTypvd4|-zn|=h=TG{ z05TH2+{T%EnADO>3i|CB zCu60#qk`}GW{n4l-E$VrqgZGbI zbQW690KgZt4U3F^5@bdO1!xu~p@7Y~*_FfWg2CdvED5P5#w#V46LH`<&V0{t&Ml~4 zHNi7lIa+#i+^Z6EnxO7KJQw)wD)4~&S-Ki8)3=jpqxmx6c&zU&<&h%*c$I(5{1HZT zc9WE}ijcWJiVa^Q^xC|WX0habl89qycOyeViIbi(LFsEY_8a|+X^+%Qv+W4vzj>`y zpuRnjc-eHNkvXvI_f{=*FX=OKQzT?bck#2*qoKTHmDe>CDb&3AngA1O)1b}QJ1Tun z_<@yVEM>qG7664Pa@dzL@;DEh`#?yM+M|_fQS<7yv|i*pw)|Z8)9IR+QB7N3v3K(wv4OY*TXnH&X0nQB}?|h2XQeGL^q~N7N zDFa@x0E(UyN7k9g%IFq7Sf+EAfE#K%%#`)!90_)Dmy3Bll&e1vHQyPA87TaF(xbqMpDntVp?;8*$87STop$!EAnGhZ?>mqPJ(X zFsr336p3P{PpZCGn&^LP(JjnBbl_3P3Kcq+m}xVFMVr1zdCPJMDIV_ki#c=vvTwbU z*gKtfic&{<5ozL6Vfpx>o2Tts?3fkhWnJD&^$&+Mh5WGGyO7fG@6WDE`tEe(8<;+q z@Ld~g08XDzF8xtmpIj`#q^(Ty{Hq>t*v`pedHnuj(0%L(%sjkwp%s}wMd!a<*L~9T z9MM@s)Km~ogxlqEhIw5(lc46gCPsSosUFsgGDr8H{mj%OzJz{N#;bQ;KkV+ZWA1(9 zu0PXzyh+C<4OBYQ0v3z~Lr;=C@qmt8===Ov2lJ1=DeLfq*#jgT{YQCuwz?j{&3o_6 zsqp2Z_q-YWJg?C6=!Or|b@(zxTlg$ng2eUQzuC<+o)k<6^9ju_Z*#x+oioZ5T8Z_L zz9^A1h2eFS0O5muq8;LuDKwOv4A9pxmOjgb6L*i!-(0`Ie^d5Fsgspon%X|7 zC{RRXEmYn!5zP9XjG*{pLa)!2;PJB2<-tH@R7+E1cRo=Wz_5Ko8h8bB$QU%t9#vol zAoq?C$~~AsYC|AQQ)>>7BJ@{Cal)ZpqE=gjT+Juf!RD-;U0mbV1ED5PbvFD6M=qj1 zZ{QERT5@(&LQ~1X9xSf&@%r|3`S#ZCE=sWD`D4YQZ`MR`G&s>lN{y2+HqCfvgcw3E z-}Kp(dfGG?V|97kAHQX+OcKCZS`Q%}HD6u*e$~Ki&Vx53&FC!x94xJd4F2l^qQeFO z?&JdmgrdVjroKNJx64C!H&Vncr^w zzR#XI}Dn&o8jB~_YlVM^+#0W(G1LZH5K^|uYT@KSR z^Y5>^*Bc45E1({~EJB(t@4n9gb-eT#s@@7)J^^<_VV`Pm!h7av8XH6^5zO zOcQBhTGr;|MbRsgxCW69w{bl4EW#A~);L?d4*y#j8Ne=Z@fmJP0k4{_cQ~KA|Y#_#BuUiYx8y*za3_6Y}c=GSe7(2|KAfhdzud!Zq&}j)=o4 z7R|&&oX7~e@~HmyOOsCCwy`AR+deNjZ3bf6ijI_*tKP*_5JP3;0d;L_p(c>W1b%sG zJ*$wcO$ng^aW0E(5ldckV9unU7}OB7s?Wx(761?1^&8tA5y0_(ieV>(x-e@}1`lWC z-YH~G$D>#ud!SxK2_Iw{K%92=+{4yb-_XC>ji&j7)1ofp(OGa4jjF;Hd*`6YQL+Jf zffg+6CPc8F@EDPN{Kn96yip;?g@)qgkPo^nVKFqY?8!=h$G$V=<>%5J&iVjwR!7H0 z$@QL|_Q81I;Bnq8-5JyNRv$Y>`sWl{qhq>u+X|)@cMlsG!{*lu?*H`Tp|!uv z9oEPU1jUEj@ueBr}%Y)7Luyi)REaJV>eQ{+uy4uh0ep0){t;OU8D*RZ& zE-Z-&=BrWQLAD^A&qut&4{ZfhqK1ZQB0fACP)=zgx(0(o-`U62EzTkBkG@mXqbjXm z>w`HNeQM?Is&4xq@BB(K;wv5nI6EXas)XXAkUuf}5uSrZLYxRCQPefn-1^#OCd4aO zzF=dQ*CREEyWf@n6h7(uXLNgJIwGp#Xrsj6S<^bzQ7N0B0N{XlT;`=m9Olg<>KL}9 zlp>EKTx-h|%d1Ncqa=wnQEuE;sIO-f#%Bs?g4}&xS?$9MG?n$isHky0caj za8W+B^ERK#&h?(x)7LLpOqApV5F>sqB`sntV%SV>Q1;ax67qs+WcssfFeF3Xk=e4^ zjR2^(%K1oBq%0%Rf!y&WT;lu2Co(rHi|r1_uW)n{<7fGc-c=ft7Z0Q}r4W$o$@tQF#i?jDBwZ8h+=SC}3?anUp3mtRVv9l#H?-UD;HjTF zQ*>|}e=6gDrgI9p%c&4iMUkQa4zziS$bO&i#DI$Wu$7dz7-}XLk%!US^XUIFf2obO zFCTjVEtkvYSKWB;<0C;_B{HHs~ax_48^Cml*mjfBC5*7^HJZiLDir(3k&BerVIZF8zF;0q80eX8c zPN4tc+Dc5DqEAq$Y3B3R&XPZ=AQfFMXv#!RQnGecJONe0H;+!f^h5x0wS<+%;D}MpUbTNUBA}S2n&U59-_5HKr{L^jPsV8B^%NaH|tUr)mq=qCBv_- ziZ1xUp(ZzxUYTCF@C}To;u60?RIfTGS?#JnB8S8@j`TKPkAa)$My+6ziGaBcA@){d z91)%+v2_ba7gNecdj^8*I4#<11l!{XKl6s0zkXfJPxhP+@b+5ev{a>p*W-3*25c&} zmCf{g9mPWVQ$?Sp*4V|lT@~>RR)9iNdN^7KT@>*MU3&v^3e?=NTbG9!h6C|9zO097 zN{Qs6YwR-5$)~ z`b~qs`a1Dbx8P>%V=1XGjBptMf%P~sl1qbHVm1HYpY|-Z^Dar8^HqjIw}xaeRlsYa zJ_@Apy-??`gxPmb`m`0`z`#G7*_C}qiSZe~l2z65tE~IwMw$1|-u&t|z-8SxliH00 zlh1#kuqB56s+E&PWQ7Nz17?c}pN+A@-c^xLqh(j;mS|?>(Pf7(?qd z5q@jkc^nA&!K-}-1P=Ry0yyze0W!+h^iW}7jzC1{?|rEFFWbE^Yu7Y}t?jmP-D$f+ zmqFT7nTl0HL|4jwGm7w@a>9 zKD)V~+g~ysmei$OT5}%$&LK8?ib|8aY|>W3;P+0B;=oD=?1rg+PxKcP(d;OEzq1CKA&y#boc51P^ZJPPS)z5 zAZ)dd2$glGQXFj$`XBBJyl2y-aoBA8121JC9&~|_nY>nkmW>TLi%mWdn-^Jks-Jv| zSR*wij;A3Fcy8KsDjQ15?Z9oOj|Qw2;jgJiq>dxG(2I2RE- z$As!#zSFIskebqU2bnoM^N<4VWD2#>!;saPSsY8OaCCQqkCMdje$C?Sp%V}f2~tG5 z0whMYk6tcaABwu*x)ak@n4sMElGPX1_lmv@bgdI2jPdD|2-<~Jf`L`@>Lj7{<-uLQ zE3S_#3e10q-ra=vaDQ42QUY^@edh>tnTtpBiiDVUk5+Po@%RmuTntOlE29I4MeJI?;`7;{3e4Qst#i-RH6s;>e(Sc+ubF2_gwf5Qi%P!aa89fx6^{~A*&B4Q zKTF|Kx^NkiWx=RDhe<{PWXMQ;2)=SC=yZC&mh?T&CvFVz?5cW~ritRjG2?I0Av_cI z)=s!@MXpXbarYm>Kj0wOxl=eFMgSMc?62U#2gM^li@wKPK9^;;0_h7B>F>0>I3P`{ zr^ygPYp~WVm?Qbp6O3*O2)(`y)x>%ZXtztz zMAcwKDr=TCMY!S-MJ8|2MJCVNUBI0BkJV6?(!~W!_dC{TS=eh}t#X+2D>Kp&)ZN~q zvg!ogxUXu^y(P*;Q+y_rDoGeSCYxkaGPldDDx)k;ocJvvGO#1YKoQLHUf2h_pjm&1 zqh&!_KFH03FcJvSdfgUYMp=5EpigZ*8}7N_W%Ms^WSQ4hH`9>3061OEcxmf~TcYn5_oHtscWn zo5!ayj<_fZ)vHu3!A!7M;4y1QIr8YGy$P2qDD_4+T8^=^dB6uNsz|D>p~4pF3Nrb6 zcpRK*($<~JUqOya#M1=#IhOZ zG)W+rJS-x(6EoVz)P zsSo>JtnChdj9^);su%SkFG~_7JPM zEDz3gk2T7Y%x>1tWyia|op(ilEzvAujW?Xwlw>J6d7yEi8E zv30riR|a_MM%ZZX&n!qm0{2agq(s?x9E@=*tyT$nND+{Djpm7Rsy!+c$j+wqMwTOF zZL8BQ|I`<^bGW)5apO{lh(Asqen?_U`$_n0-Ob~Yd%^89oEe%9yGumQ_8Be+l2k+n zCxT%s?bMpv|AdWP7M1LQwLm|x+igA~;+iK-*+tClF&ueX_V}>=4gvZ01xpubQWXD_ zi?Un>&3=$fu)dgk-Z;0Ll}HK5_YM->l^Czrd0^cJ))(DwL2g3aZuza7ga9^|mT_70 z))}A}r1#-(9cxtn<9jGRwOB4hb9kK@YCgjfOM-90I$8@l=H^`K$cyhe2mTM|FY9vW znH~h)I<_aa#V1xmhk?Ng@$Jw-s%a!$BI4Us+Df+?J&gKAF-M`v}j`OWKP3>6`X`tEmhe#y*(Xm$_^Ybbs=%;L7h zp7q^C*qM}Krqsinq|WolR99>_!GL#Z71Hhz|IwQQv<>Ds09B?Je(lhI1(FInO8mc} zl$RyKCUmfku+Cd^8s0|t+e}5g7M{ZPJQH=UB3(~U&(w#Bz#@DTDHy>_UaS~AtN>4O zJ-I#U@R($fgupHebcpuEBX`SZ>kN!rW$#9>s{^3`86ZRQRtYTY)hiFm_9wU3c`SC8 z-5M%g)h}3Pt|wyj#F%}pGC@VL`9&>9P+_UbudCkS%y2w&*o})hBplrB*@Z?gel5q+ z%|*59(sR9GMk3xME}wd%&k?7~J)OL`rK#4d-haC7uaU8-L@?$K6(r<0e<;y83rK&` z3Q!1rD9WkcB8WBQ|WT|$u^lkr0UL4WH4EQTJyk@5gzHb18cOte4w zS`fLv8q;PvAZyY;*Go3Qw1~5#gP0D0ERla6M6#{; zr1l?bR}Nh+OC7)4bfAs(0ZD(axaw6j9v`^jh5>*Eo&$dAnt?c|Y*ckEORIiJXfGcM zEo`bmIq6rJm`XhkXR-^3d8^RTK2;nmVetHfUNugJG(4XLOu>HJA;0EWb~?&|0abr6 zxqVp@p=b3MN^|~?djPe!=eex(u!x>RYFAj|*T$cTi*Sd3Bme7Pri1tkK9N`KtRmXf zZYNBNtik97ct1R^vamQBfo9ZUR@k*LhIg8OR9d_{iv#t)LQV91^5}K5u{eyxwOFoU zHMVq$C>tfa@uNDW^_>EmO~WYQd(@!nKmAvSSIb&hPO|}g-3985t?|R&WZXvxS}Kt2i^eRe>WHb_;-K5cM4=@AN1>E&1c$k!w4O*oscx(f=<1K6l#8Exi)U(ZiZ zdr#YTP6?m1e1dOKysUjQ^>-MR={OuD00g6+(a^cvcmn#A_%Fh3Of%(qP5nvjS1=(> z|Ld8{u%(J}%2SY~+$4pjy{()5HN2MYUjg1X9umxOMFFPdM+IwOVEs4Z(olynvT%G) zt9|#VR}%O2@f6=+6uvbZv{3U)l;C{tuc zZ{K$rut=eS%3_~fQv^@$HV6#9)K9>|0qD$EV2$G^XUNBLM|5-ZmFF!KV)$4l^KVj@ zZ4fI}Knv*K%zPqK77}B-h_V{66VrmoZP2>@^euu8Rc}#qwRwt5uEBWcJJE5*5rT2t zA4Jpx`QQ~1Sh_n_a9x%Il!t1&B~J6p54zxAJx`REov${jeuL8h8x-z=?qwMAmPK5i z_*ES)BW(NZluu#Bmn1-NUKQip_X&_WzJy~J`WYxEJQ&Gu7DD< z&F9urE;}8S{x4{yB zaq~1Zrz%8)<`prSQv$eu5@1RY2WLu=waPTrn`WK%;G5(jt^FeM;gOdvXQjYhax~_> z{bS_`;t#$RYMu-;_Dd&o+LD<5Afg6v{NK?0d8dD5ohAN?QoocETBj?y{MB)jQ%UQ}#t3j&iL!qr@#6JEajR3@^k5wgLfI9S9dT2^f`2wd z%I#Q*@Ctk@w=(u)@QC}yBvUP&fFRR-uYKJ){Wp3&$s(o~W7OzgsUIPx0|ph2L1(r*_Pa@T@mcH^JxBjh09#fgo|W#gG7}|)k&uD1iZxb0 z@|Y)W79SKj9sS&EhmTD;uI#)FE6VwQ*YAr&foK$RI5H8_ripb$^=;U%gWbrrk4!5P zXDcyscEZoSH~n6VJu8$^6LE6)>+=o#Q-~*jmob^@191+Ot1w454e3)WMliLtY6~^w zW|n#R@~{5K#P+(w+XC%(+UcOrk|yzkEes=!qW%imu6>zjdb!B#`efaliKtN}_c!Jp zfyZa`n+Nx8;*AquvMT2;c8fnYszdDA*0(R`bsof1W<#O{v%O!1IO4WZe=>XBu_D%d zOwWDaEtX%@B>4V%f1+dKqcXT>m2!|&?}(GK8e&R=&w?V`*Vj)sCetWp9lr@@{xe6a zE)JL&;p}OnOO}Nw?vFyoccXT*z*?r}E8{uPtd;4<(hmX;d$rqJhEF}I+kD+m(ke;J z7Cm$W*CSdcD=RYEBhedg>tuT{PHqwCdDP*NkHv4rvQTXkzEn*Mb0oJz&+WfWIOS4@ zzpPJ|e%a-PIwOaOC7uQcHQ-q(SE(e@fj+7oC@34wzaBNaP;cw&gm{Z8yYX?V(lIv5 zKbg*zo1m5aGA4^lwJ|bAU=j3*d8S{vp!~fLFcK8s6%Ng55_qW_d*3R%e=34aDZPfD z&Le39j|ahp6E7B0*9OVdeMNrTErFatiE+=Z!XZ^tv0y%zZKXRTBuPyP&C{5(H?t)S zKV24_-TKpOmCPzU&by8R1Q5HY^@IDoeDA9MbgizgQ*F1Er~HVmvSU>vx}pZVQ&tr| zOtZl8vfY2#L<)gZ=ba&wG~EI*Vd?}lRMCf+!b5CDz$8~be-HKMo5omk$w7p4`Mym*IR8WiTz4^kKcUo^8Hkcsu14u z`Pkg`#-Y^A%CqJ0O@UF|caAulf68@(zhqp~YjzInh7qSN7Ov%Aj(Qz%{3zW|xubJ- ztNE_u_MO7Q_585r;xD?e=Er}@U1G@BKW5v$UM((eByhH2p!^g9W}99OD8VV@7d{#H zv)Eam+^K(5>-Ot~U!R$Um3prQmM)7DyK=iM%vy>BRX4#aH7*oCMmz07YB(EL!^%F7?CA#>zXqiYDhS;e?LYPTf(bte6B ztrfvDXYG*T;ExK-w?Knt{jNv)>KMk*sM^ngZ-WiUN;=0Ev^GIDMs=AyLg2V@3R z7ugNc45;4!RPxvzoT}3NCMeK$7j#q3r_xV(@t@OPRyoKBzHJ#IepkDsm$EJRxL)A* zf{_GQYttu^OXr$jHQn}zs$Eh|s|Z!r?Yi+bS-bi+PE*lH zo|6ztu6$r_?|B~S#m>imI!kQP9`6X426uHRri!wGcK;J;`%sFM(D#*Le~W*t2uH`Q z(HEO9-c_`mhA@4QhbW+tgtt9Pzx=_*3Kh~TB$SKmU4yx-Ay&)n%PZPKg#rD4H{%Ke zdMY@rf5EAFfqtrf?Vmk&N(_d-<=bvfOdPrYwY*;5%j@O6@O#Qj7LJTk-x3LN+dEKy+X z>~U8j3Ql`exr1jR>+S4nEy+4c2f{-Q!3_9)yY758tLGg7k^=nt<6h$YE$ltA+13S<}uOg#XHe6 zZHKdNsAnMQ_RIuB;mdoZ%RWpandzLR-BnjN2j@lkBbBd+?i ze*!5mC}!Qj(Q!rTu`KrRRqp22c=hF6<^v&iCDB`n7mHl;vdclcer%;{;=kA(PwdGG zdX#BWoC!leBC4);^J^tPkPbIe<)~nYb6R3u{HvC!NOQa?DC^Q`|_@ zcz;rk`a!4rSLAS>_=b@g?Yab4%=J3Cc7pRv8?_rHMl_aK*HSPU%0pG2Fyhef_biA!aW|-(( z*RIdG&Lmk(=(nk28Q1k1Oa$8Oa-phG%Mc6dT3>JIylcMMIc{&FsBYBD^n@#~>C?HG z*1&FpYVvXOU@~r2(BUa+KZv;tZ15#RewooEM0LFb>guQN;Z0EBFMFMZ=-m$a3;gVD z)2EBD4+*=6ZF?+)P`z@DOT;azK0Q4p4>NfwDR#Pd;no|{q_qB!zk1O8QojE;>zhPu z1Q=1z^0MYHo1*``H3ex|bW-Zy==5J4fE2;g6sq6YcXMYK5i|S^9(OSw#v!3^!EB<% zZF~J~CleS`V-peStyf*I%1^R88D;+8{{qN6-t!@gTARDg^w2`uSzFZbPQ!)q^oC}m zPo8VOQxq2BaIN`pAVFGu8!{p3}(+iZ`f4ck2ygVpEZMQW38nLpj3NQx+&sAkb8`}P3- zc>N*k6AG?r}bfO6_vccTuKX+*- z7W4Q#2``P0jIHYs)F>uG#AM#I6W2)!Nu2nD5{CRV_PmkDS2ditmbd#pggqEgAo%5oC?|CP zGa0CV)wA*ko!xC7pZYkqo{10CN_e00FX5SjWkI3?@XG}}bze!(&+k2$C-C`6temSk z_YyYpB^wh3woo`B zrMSTd4T?(X-jh`FeO76C(3xsOm9s2BP_b%ospg^!#*2*o9N;tf4(X9$qc_d(()yz5 zDk@1}u_Xd+86vy5RBs?LQCuYKCGPS;E4uFOi@V%1JTK&|eRf~lp$AV#;*#O}iRI2=i3rFL8{ zA^ptDZ0l6k-mq=hUJ0x$Y@J>UNfz~I5l63H(`~*v;qX`Z{zwsQQD-!wp0D&hyB8&Z z7$R07gIKGJ^%AvQ{4KM0edM39iFRx=P^6`!<1(s0t|JbB2tXs_B_IH9#ajH0C=-n+ z`nz`fKMBKLlf?2AC+|83M+0rqR%uhNGD;uKA6jOjp7YDe^4%0fRB<^bcjlS2KF~F; zu09wh1x0&4pG&76M;x8$u`b134t=dEPBn6PV|X29<#T4F1mxGF*HOgiWU8tN@cguI z_F@o+XL7FJztR63wC|j4x_DANzcX94r7Iz-O2x$({&qd*mdLG=-Rv)uZ}UlMR+F&q zU}=lkfb0p1>1Ho){o$@}mSKIV;h*$AND7~Dl)QzpFBlSM99Kx+F7GsVK5xcR? z_4Q(Z%cgk8ST}U;;=!LwyZVu^S$>B-Waeik%wzcKTIqeX=0FP(TGQ=nxi=dsS5BYF zl@?}NT!Y!Iyos^@v7XWXA{_bV~1lxz7gC?xuXxy0_?GaN!AhRRM5>)^t%&ODd;@HN5L{MD3 zc>i2keQZVm#?NrDwbfd}_<*5^U&w0zv~n-y8=GGN-!=_`FU^cM8oVCWRFxw?BM^YD zi=Vxz4q|jwPTg+?q7_XI)-S@gQkh>w0ZUB}a{^ z_i;`Y(~fvpI!vmW*A^|P7(6+@C4UeL2WATf{P1?H5rk`5{TL zcf!CgP6Mi{MvjZS)rfo7JLDZK7M7ANd$3`{j9baD*7{#Zu-33fOYUzjvtKzR2)_T1I1s7fe&z|=)QkX;=`zX8!Byw-veM#yr;|wjO^II>!B*B z0+w%;0(=*G3V@88t!}~zx)&do(uF=073Yeh*fEhZb3Vn>t!m(9p~Y_FdV3IgR)9eT z)~e9xpI%2deTWyHlXA(7srrfc_`7ACm!R>SoIgkuF8 z!wkOhrixFy9y@)GdxAntd!!7@=L_tFD2T5OdSUO)I%yj02le`qeQ=yKq$g^h)NG;# za(0J@#VBi^5YI|QI=rq{KlxwGabZJ0dKmfWDROkcM}lUN$@DV`K7fU?8CP2H23QPi zG?YF*=Vn=kTK*#Y_{AQN&oLju|0#E=fx%YVh>S{puu&K$b;BN*jIo@VYhqPiJPzzM>#kxoy0vW9i;ne2_BIG0zyRFp<3M(iY(%*M_>q0ulV2K}Tg zkG{EWKS{i%4DUuHi%DVKy%e+Q!~Uf`>>F6NgD{{I8~nO4!VgOvtFOc7(O)X`|7n*f zxBa4CJ-v9fUUH+`7sPVvpM_C*udZ@OTGTzx56QM5y~OlrZc&w9=)B?nmd@keRn+^= zvm~4sa5987LFDnU{(N|N zJAR8H@}p1fC+H(yTI4n#%~TbImMpuqYn9cQ<0QQ%=PzZItLkC*ef9WJUvfITKWh#D zc#__8`4am9%#NslIUw+<82#SR8AYG|woLfBg#!-&dqq}@P>|I0%lbdy0lSMmNe+}o zj0zZuFr6Wb?Y{Qy-S=|r`bdrDmhnmvkRnkdn`YCleU>Q$=je}LGhh>_QAj6aa_0Oc z%Swsmui;IRx7bN*=AAS@5yW&Y2hy;3&|HAiA8}!HT6!Z!RVn~MZg`RmI6&%#tBZDx zfD+y@Z~NWlk*4l13vmt3AK2wP!fQlnBbECL>?p)F?T)<`w&QN>cP_V>r7UTcsTaaP zTOb$f!P@zf$6>890NVKbIkG8rE?9!Y97sMSZjfF?A zYR8lp`LMoz~O?iaZN;gcX;LC-%Ia*R%A&SLx!YIf29?P+=XAAojK8!^OU*@?R&DK!#G_lsn!#;S375uZ&B0HH1|BO0R90$U>qs zSvHv>H~mAgNCcjo-e+;RjY6B9NCbQrZ|BHjTkehaU<9CSkdd>Vl*ifA2LNOP&R2Qdy3k3-TQ+ zbq=#vI43x`s=%~cGyN&y4Y!FxhwgDe@i6uv8^BLL&3z*SO=D0aLjih?gY4-9uWp5or)H+v~w6n5X#F-I52z=Z_p4JB(;M| zeaVFhuR2|3UD2MzVc~^nSoD2(dD#uL_1PdnIxeA{V5n`#3xf1Zx@4lw(DsQ&H$h zw#%3O<1173hjg2_nhKi!d1ej=h7y`hVjCNB6|HTnx>SWuCE-kgTnfT+YGX4_Lun({ zDv2`>d3vrS)tTf7ps_vvh!Cx^e1BFuWnEAh0(7fkNk|-3oU|iRWdsC6U)?Raft~HN z;^$U}vZK5O8|LV$>6X5T(uYkblv{zwPxnQBh(BQ5tA~J!vGiAMYP^_ki~pkIxDfOZ zUJDwq%O~WueeV6%uN<54&u*c&E4y431cklBNrb06zGOOy4XNT~JS-q(s6@)F@ovbe ze`fial(O4(-su%6@@1+V0MsdLLMyE8;)nou(7}czU(5ASaZYDT(kUZ0L(&g$nF^n9 z9-Pi`ZZLX&)^*M6As4_2Mmc9S7OT)F8KkL2NJ)KJcnCuWU=Wy402A&45#Q9Id~BBH z0cY*xlv!uXzKrXLH!xQu(OtJvEj|0-DmRj1vjFz{c*I4$Pe(+_V|^b~S!0xm{8lq= zZv)@NlcyL3Xdz+*|L137F7y6L-2VsrKw=q^S>F6i%<{Fr8zk06$Ay-(!L$fY@7mcng!2}L0t zgi|KxfB63Xtk_Q8#ZPipQ@!zgjdpEIbK_?q17Hoi4Eiyun$hrc>T(7pOLVLQE=lgGwA+A308p& z7@=09(|$>eLy5gLe{*|3b(M;1n;C^~v?o88jYib48eR4$QGsBFzd}3QuwO^_XE(=B zq+hMi0UFC|dB{LCwch7;zYT=NK})O%sgi0k#yV;My@24^B1+CuZmYOh0^b)5Ba_)) zC%i#_Iev&nsu%I|1N5=MVc#PrlunKAs&hY|3s5;@}`>sB>}gzxuB zB=2vrRyB3uiyW(hkDUNe1@&(b`;>ZvGgw|@s{zVC#_`HXIN_^J@Etb zA7A+F?ot37T{<-vTy8h&b3e+WKHE1oh;pUQrN4yRRrx?mT_9jRa2i4l1fUnLW^Cbl z!I1>VzyFe?VELWWhM?@?t-YPZkD-Qjo@bC2(o#ZtZmr{KZsdFWItV`rs$gp{724@C zL8K5}E0+DHcWcL^{BGei4>@J-3%a#$y6;I}=upc};-NDv-z#kPX26ylOpH)Ov1uU{ zkLj6oiH6l_s+B~_z;|Jc2oi?naS7#3H63~~lWj4rUnd=fCnKdkik<@R&kch9q##G{ z4u!%=rlM~Yp3jk*t8}1B`Sv6<%Z^}~1e@aq zg|JQ`QO2pSjAm-g*?IrNc$^~sIrNBo2$m|Sxanr?Mfs>2@Auu49 zGXlsS<9XS1&8h(dD*Hl&5HBDG!^pJ*lkau_Ur+7`7z;rcs$hT4we?3bT=7Fe<>{5( z2m2(c+hUz2BTHM8dCe*Z3XX&Av;b~a=$6EF>&^E8%nyxO@m_n!q&XD^A{SRjRZQ0L~qDeC=j&0$j6=LNIz@`ni^>ch|sv}^6 zlm>?28yPl@WmDPR?Y-A9X{U9Dv_IsbXJnzKCjkRksLOg#42uG2mE_acbTQ4)J|1V>%U@K(FP3AYhL0U zdeOCPN1qLv!|#c=p!_+%VNV(GHt`RuLRV^vz<5tt-r)yOK**kUWPspVAf|}ZL{LS= z@k(@@!P&W!>wwe`x{+GrFSWhHov7hu?{KuuT%kl#WO@*WX$i_@retlhQBj++SVNCx z5$78LxP>Z=^aJ)D280r_jj=zFfMJFXCIe^B{~V@d1rl_F(qo&AB4bC-vYL>x2jSKX zpuTG-6kgp3e^T&+dtV*i6a~)v@n?n*MffN59y}<0djUX zt27R+SE#hp8bzc#;rk$jw3r4)Q@eI$*`_)=Pvge8@8|8>H3X)<9YX6cXa=ii#Le;(qKm@%0-7$>2ShnYc`j#zJ7gu_FE^?uAkL|H)UIH#gPu^40!6^J=^ zr`}iwa^!4tzW~vOMZAaKF>*8A{^8m$i(VK)>?=#l`xrVe>wseSvM_aF zATNkY>kM_P3?1kE`uIq#mvr-wuTgUH0N<&JhF=(E9%^NS*HLm!4GZ4_XI zL=R5tlG5Mk_1rPfg)sk^llFuKPMPBhuU|L5q#yP_mzxp1o&pAzi-X31sgFpIHn@($ z_>=`AB5(8tP6p2zS5VEvH5J$M` z_much3>S7t3Yo`Yx!>83-hW9LYzDKP?mKdkD#QAK8*M((sx{eBQdrR<^3ZhFP81+& zBnJMUefQyNBji~$5d88Wfw1Lv59aJN9t2!pABLg;ewJ#LXL-10;QcJl+Y4Mtngb)k6JZlCf)3uD_u)J3sYyN;NN5hNbg$%W!i-GK%e&!Us)2IExWSss$YG(hm3kJ-h%yD z>8q^n$+4I(_y_mbT{du4P%h1j3oSpjhY97{+IZ`aA4ug!vNJ6*p?<2H(2w+GD3j$I z1TUXGyNzdf>_yB3grP~FZUs<2Quw;eEi*7s(-MiIkQ%@J^+WGdQvYSUN+TRiD-xto zJ=OUU+kxGYc!HCLNbCvR4lGTp~#L;DFzGd-#gJe*xf(P3hDQz|y)?b9mwU3WUVnpcqXM<@w%r-k*Wr^gzAv)8T^sqA=Ye z!7qy&exJmAcAt~CwS#@yNmjr8*T*!A6w4~E*ibaLRs0CFo(;R3=ODhDt6zWNodmo0 zXx&bT$6&+5c>a|WJ)F4G-^GjY0H#*tY=UNyYr_q5fsrcjk(c^~e*7Lf`!Jd`)p412 zn|^*hV= zFI4UbwA%X@smDd$cQOiMC%jfitTxTb+#`9`G=2rJDfK!E=5ra|So>lc{X1$~w28i+ z4p&cTGwZ#5VueiXS9O8#;RR$yg7tL9!^)Sz&pZYIzlSh}0}V{LxL$Cu%B4U5_}k}- zm~|CsD<076x@<>m=6w6N?WaThIBP`!u{-;WF)xc=2otx*lwf|5+MkdJePjh(B z9SH+%cHGCMAXNxB{_3^otDWdsV7Ob6n{0 z+&!(;iaHOX__5z_$Qk{%xYV%Ig@7iokGBwR`3642ZP#H#v9QGbWl8<|MS*=@qO@Uj z6+SZ_v9`1paUe5tFN~v(b#J3a_Lx0+;r9giZIx-A5TxdbG>xi#AZ5_z1V}B^n)sxT zz49}eK7EWb6wR!6-qQOrHQHkUvshvq%=G2d&@(#XM*Am1;WbnJ{X_!a{ZkphD$^TQ z=Iskb&}=lBm(RHiwJoGg`*NiQ6#RB$T#LF+>#ef;Jne&MxKPX!#r`&TVEFsp2jnNx>dClzpcPy&G&13a_<0qaR3i+k212~hoQ z8nMk{JP-t04I{GW5gUBqcJW-jSMrlw}>p)ptx?WKuCUV77taMiV zHok9V=6yv+Uts@fMY&A}amC=!Yj}eL@=e%XJ#%?agkt1jWF+10{(E9mHLDa>Ll7Vj zG=3cp%ljIB-6pC}6&`xJ*6WCP|IlglLWJ^?yviI8Ve)?V_i4%n;olzny62_`-|IGi z^=}p_O>Z8M;c4|RExu70E7ePW(HWVS&E$+LL6xSQgB`QfMQJ|4pCTFowA39p5P-|$ zUtM_H2HnP8_RoS~Vwk(FhbG zH41licj%=0a;Ln2STFBvU}Ne&O&%8bYKj!h1FA#sNM`232fX|U3QPp#3C?mN2;hE9 z;)!@5ixSPl<89^7gwhHc2YAX1KJK$#*3`KOMIQ253q7-*RJ5k)zp9GBO|Ga~X*^}US5oN@aG&waHV%vi~r{t^`ptTxb zL}q1W8S7*>7oWwvgV4uFLZ(@k`R*=LO_|Gu`prs~!WQXj-NLIa^2(7IHg>BG^N zc|i{-^=&Cek9dkJFQys|sjG9i>LLz|;yCv{^1i%c*h>8zF91kLvS9HBQi~ZU!JL`B zK8N+U0fr1*6??Ium)AF!6tc1eGhXIYL6IRT7rmKp7+>?%5Pa6zC5)KY$ycF0ZJ`G5nEQDG100U-jLkH8^UE4g6wq?sg%pP=-$&G#bcN`^?w3a6 z((s$6eRKcSEIslW-kk5Qi|5Mg-(xdLF}PxxVh$PuO}#aR6pW1kV4Af!Bqh*btXNNZ z>-4(IUl+L4dw+3LcpGut=qB45O+W)Q5?*zZ2A6rJcg`qkSvWA!j^r2mqKuCm6`Py? z@^T#Ux04HemPGd!Hs7NkZdVn1}8_j`o?)*OKZGS!`ff)gF zG?v-lj$wWNWCcw2Mg2o18D~1?3_b0XzdiKBNkYSDpcv@&kp0POmweJE2ZkIQ3B!a! zIgIoE+Xv?;34kyo^QYjZk+tEqZvq^#QG(OzX4~X+KtsoQoddTWUR(yo8R+ObEF1j<-syWOb>)JQ&Zbdu(sctU%Mt zW&YR0{ttY2TTXYZ?~WNU&cES1Z2q(7SrWDh``!J(JM+Nk$!hu&Y;(7E`ZNKTe0w+% zJc?Qnw2B+%UR}0;cB0Rufa(7-3FF}?629@LgTiEC&2uyL6NxexOp?AKT^aAx3gi(W zao>r>MPw0eQ3>IV02uLsC@>yK_epX6GRg4{NEL2wPPF9=*L2RV3yyK8DhuEK>rmmV z`&Q~#c`lgR&93TdOCja|ewOXmPNRh7!&dMT(1ett#iDr8HZW~VqWW@7fe9B6;7S+? zbC`d4@MEau&mKlOPKd>*10q0c{~^baw6!a*w^sY#0Xim{oOsiXiDOhbG&kl3c$$n1 zMRrD83&QucDSEcV*7LIp8VTA@F<%qe+_c`L;6on(>SjAU^}5c9!BCffT>$VQhe=)z z8(=Ej{5>jhmjB3{xDfj2R@VmHQ!CqjlO4KnuOmvHy3K#po$yp_V;p_MKjh1`(rzj6 zHW956k1yvntz{_g?Xbs`avK(IjlTnsu%htO;D7 z?J#x^EzuvVn&NA=!MEj7cwe5A-Z$Zk2LBZH$~%E* zf`((xH0?`}hs|HA%mtwfOEsZJxxrennkTYcwP#FKO5%Lpc^JXhSpV|ZH$Wr;`}`_( zIP==gd3LYyVtwD|*ZJGi{7~x8{=^bGVqu0RJ`n_BZH9+}kz%-4ZRsImi@rx%=ZEKs zcPnUXo6hbJV>fH;@1|bAHIe0ijYI*&kdT|HkDS$9No9 zCHo=*HWb~U+Dtzxr+Esao}6@|;Pf+E$ay0$kQp#s{wlw+7aIKbMdf`OqhoG*;Tco0 zjrP}VQG#Y2cJuqoJg&5({)S(BA}q9T1lGeWRyu=Je|)I!6a+aj!IP^1({)ZYe&x6w zt3a)Dq^TB+A7CdB0-}#z2Ur$W&h3YVw8==!xONy$uQmDWh-@15iEOt!q2m&?ZLA|w z8loSb(0}7y6Xu0?M5Uf4>VZGluB`wMf2oh;m)ghxVda>3m}4%V)r^0nVQ5V6f3>*) z0&VN!N0~GC^P}vj$`EDMZEmVV;N&RISY2C;$0;2(<{Lt&PKzqRByQdiEHGAbwtbS zPj`Da5%U6k1oEtVzI}QNw;!hT6F+~|@=c@$C4NtO@=xgP?|5MyZAyuCzcvq4rdAv@C06%gZ`9%I);R6UGiGJobfux+<0DLS&|MSG4UH z_~o{^^9>ixMg~mY!-@Fai{xaE4^;qy9iZN15Gbn5ZqHWf>Jc5Rv6(#n8`1NcCsdmG zab*dSXVPaE?)wCalD;$ivF%@nB#7D`@YG04p6ed9m}4iJW|pfVMLE<-c{=-8$e?cH zUdU#mCj4gb zZKA^b9p*9S(}8@tw~1RNPHr7tQr;P+-)D8|sq=*o)G%RGqt> zzP5yf`pVxb)I51D_G~Xp^GNK zVI6sAX)a9s)e{8N3?35YA6aQTXuyszK3ah~CemzA&CII#8F&F#KN41~8I^&_%}6MCNb{W87qAF`zj_Y^szhb> z3p3}KbOxotY|(lD=;)`fYE_*{S}x;f^SW#)SU&5X#o|-R|trpa|L5PS5aa0 zTHw8%SDSVtU4?vyrhnq+^@dgFS)|(y{~(4j%3UEiO-rBM9%`)8(dh33pMLiuurNY# z#10AsQ7%*0Cu_DSAU}P;X(JwA64~Q_^R%d_zSm^6Aux?Pn70PM>9EvLeOX z&w9c)pGmcL22;MO3C_B>=NC0RJpMp8?#ZUf=GWRvy z6RHq3B}=MGVg?9@iKFBpsvnkVh3{Vpp=`CcD=u~@ql{my|6?3ssi3mCOPnjI&E}VC zc@X+Yl>;;DNo0W0`0th!X{?luDhOC{E8N=?!w}K1{V=)+1={m(f`Oc|N=07>}3;z{-(A zm{JL=j?Sro5iecmE2-pWlRf(r%|HEQ7kgwQ9+kt=NBhtQI7OwcZ#3%$Uf%^r2nhjY zoQ08MfC%_X{O9~WcirMZMhn#z^ux4Erx-tf-6bHD)9eH&^L>^jvAd^9A^DCDs?0;k zkm7LE*KjP6`2d17MrQaaLqd_Rka}J$csvUec#hw78<=s(hyR>065~YCVCA9+#Q+; za(*L0IEw!r5P|@-;x33L$Lv9 zcuN8YG&g{<(SeJG18~(b!5yywSqQiLAX0;---;}mF5&b4lg|T?LwKREa{9YX_-zL@ZE?Zqi@HxK^2KO1>0LATu{te=T zprmHtY)bDVfxI1S}KBE7V zznP7KQ8HekWU#W6mw`dr-boV}pMQR==&5=Q5T=_q091jfc;R*jX#&=MQ%~@E@9^?`$v48ks<>(fI(F6L(5ppKy|$HWng*bKOb(4|cMUB&z$#ob#XV z5-mg)gmFIybZf=znm3ZPyUO^GJfxt0kmHjaTZ|sthsxXw&}Y)fOUSg=JhRSR^UjZ- zhqqb}Wsyw4zdnj6@#BAJa#-PdI4_dgafFXh85DsEQ_cT+5)XpZq$fZlBA_9UsE9r6 zEFec5?uqN@QhJ^IzwZrwl-5J`CmVPv{(YDTqEqWR^dI;5hXc~cxP%B3v&~s0`Ct89 z@S`i~a^c%V^N81dDT*ItFS*&IN;@O$EgzX0e7x&}TD=!zS}hTpezBLS>mdX(5< z)8DEI(-o_D)c-UX@dA1MuJ*yc>Hf4|`*B2S_O>w*-tbUwtiu`;W(Ud{HTty@(&x(T(F&;M zJ=?H>6`B7nf-90e8V`WSVp|0oEKB-P2M{}4ZDawzvM&a!y>`Y#jCsD%T_l``@ah(I2nJs~Q|%uSKu@k!m~*8B*IoA{*TgtF<(5sHCGG;n@NE%~Xt(G$^&<87u;}Na zx-8cq0g`uA(&RBFo=-4Y1GUZ<``Zw{xL4jfHkZw~%~wvtGueszcXt)_QwH8g!; z%s&3kSa~R$dO$-%L-)c@_hi7&>{6L_M>OZFkUQu;{sL_bUMStNrt{{&O(Wn~*zPOk zB>dnfszb29NSTf2pqIs68k|p-UrSrxgLHqi?3N-UFa!LHy9n1)=s>`yS+J{MEzS@ zNlfGtpma7kG&LR3JE@wB%rFA*h~~KitlO=IP)ZjN6dQLM6qsry zHkB#cyNh#n`)}bCrN1My*;k)^@>e4gJ`LJK?2)Pwp?4Tl4)4FA0(tvY+#1jOUM)xw zlMz4x-f@g^+yKUN`?Vu)|AwujArnM~Pa@y*Q9S8eS(u{-S%(Z5=R~pRl5ZGDjdqH% zC8rW&{##wOpU_oTIG4WXMk4&%2t1;lWcW5&!yxmOT*!hBcKyTqEcNoO+R2;Q?Yj+W z1-Y4?59fijz4(MIDwGe4-baYf08UCs;r|YefD-Md2ST;=cxwpgW=tR76-dQVAhn^= zG9Wk5lQk%jIR@KNU!UMp6@BfU;r+;y4VQ)D2!Il9HX%yW-9nOzV+m$YKzVaO`B8S7t z$!S2Mz`xw>V(RjE`0>bQp<0y&h~Y=M#jpy!#=dE>`=e_AjSZq6u!Dy1xJf~-7|0F! zPR9|n`e_7D2DIV2H(CESQ}hA>U>n|6`%z?YKEA~)BOVY%y=jPV zT=44R!L?J)736X#csn|lfBJ)o8ixaZclguWgrGO<`TN2FMfO}7;5}d+BlK0yTSH3* z4!=;5rOh85&2|x=46hkNaz?)U8&=bcfh=N_#8BNpZ2v$aVBo;sk^*X`v;4-LU;D>! zM*h12MxXIQy)SfAqE4;jY)wgnppazZkdNNVVF;(PLf^qK$FgY9+VFyBKE7UC|f z`R|?&egV11K3s$rJ6!GvoeW=jV*!-e(wA;x(2=d0E_e_%0x--0o8#~m^H1%AH5Z^B zn!TNPn927*bvaf0pt}zhK0o^V@WlGwwKo(*nQ|Q~4_;>~-8y20`HP>@UJa)3nEnGG z5Hwhs|FcmFG16ZVNb5hL`2Gc1{zWIMM{_OiKewV!hCi}U!VuE?s9wU-QbZ!)+Y^tS zGzp5OSi5iq6hmEr$w}&9DFgoB+i*`q`8TBi^MVS{SKEb8Aw%@K7@XCo(De2A`6%mf&a2#~y1N)+kJLD$1HCP!22)(U}xo2|j?WRzt(11j8Z_*v;P$R+Ug*Gy3VxV4K; zGGUGabnW*`Z}~`ydXL-l9e=GC$pY#z|63vy>E*m=$=j}iWP{sRTh0%H54`t>2xYH% zsk+M&u&pNgMCM@3e)Xc?jBWX-TIR_cQ1Z!RW7!B zBjZX=+^3}?SE)B+$EP+0oi1Fp5blDT?*}nsP>filqXH{ms zxU<$hetC`u)Wi+x|EKL-`y^#aQX+sDYIa{M;V%LqLrOk~lR>u0Q!+pyQSU4zY`?E^ z|5@)C)w6G_=i5YYC5SE_u(7hDNYr}uKT|@DSqF%S++lTIbIk^$a>{~0IH8KNFEy%+ zW#$&!ynpgNJh>6uR~?2c)ZMW+h0OKu231(7L_vETPaR+(P)Zy%0~yGm>E9?@@x!Jy z3PYgS}Q@b}x}E#F27@F+j}0=&Ql4gES&f8acMrPAVlVs9$97`FR))R5wI zc&}KFI1UIewh>3PkhnB7u zS3AT8_*|nexznG|Z*DU0c!K@jsI4J)5#DyNi#|e#`l1Vv1`1)*NVcy0LZ``aL0n8B zecupJ(rhq3u8bW0NIRhKYq$v1li+jp*4hfAd&wxYDE8vn1TQ7S@bTM|I2Ob z8vMOIxA7&_j{AKmD+O@EyXT`|dElt0pED^@IV0m)RPBUs*5jW60>>w1!@_G3aBKzG z_f(KfAPBk}-jQtR*Sroq!*3rbQ_m27e+YdzQjUb<_*k8vc_C)y!@cj5E>NxUhPu&g z@Z2<~esU`)ih+4opWe+K7sbN9n*9@n>#@n3*o z?xoROgDuvhq>jJ;Ve{6i<3roQNfgo5^4Q4(|GNExO2Dr7GjgA2zWuKp_K)K0R(6lv z!l$!zW-+T6mb3gQaAFviTQi{|*t%>{(mhTdy+y;Re4qT@kccy#{b z&zWy~kLO@>*WPj2k#H)|7L&gAJ37DmHQAme#@m;(Y8Nu^`D5vf8sZFW#+lA2!HK=( zJ)#hO6JD*`o~&c*&46d}g=Qj@SsoB5ikC z^1V8E+&<-OzuS_C`p5<<(A6fB`LXT(!kV^0_~hL6PpW4={l%|#xgdh?5EIk~lu8{D z2hiyhv3Yxij_#$Wu>P@7SYsl`-~3;}Ktx{34_NL^Kwin&=?!HDv3elQDbcU*qyYpN z(#yw~f1vFGK-t%CC-qa-4FYHbA^h>bag-I&*qaxwn?Qv|idE$<>1H|Gr6JtUu(he2$eg!N z@HTF@dG1)*y;4fxe)4_ZkpaBHH9hXp9p4|gLrRQyuevRd@gSS}JhRnWqrvm|U@>qM z=yl7RQROTKwQtzP3!zUF)_6Ld#NGA6v~2{J9Dd`h6{%+XsU#qGLh%`fB1Hc?wfayK zN`H4BpDp)npVQuu$DVW1qsBS&AJ2eP%6Qw>;k{)Z$8%HL=Q4(a$Ng2_vHw&vA!1L+9zc8vaX2GtqJ{L-;gvF0IR$em zMQ8@{Qp3+3Quk)TJ$?I<8KmwzD*7#(q<@Mc`dchngW}cRG14(Z6K7{T|LhFXwhqUQ;BET;cYqPcAcMgt6M$V9$(?jHo@Sud$an$U&5F zZ1QNh^ztt)E*d#Ij;<43oSKKnd+WNr$_r}+s_O_x6DZSB10*5Q{ourqq>mTl| zx4y^(cy+9;t@R=*j>3_dmm_m)$k$#937V(sllby&5)Xex^UD-|m|q<(jEd#@DV(of zAd7sSdmS*zUDqJ9|K%O2J2OfdUiK{{b{PCy)pi<;hp~7v1CQj&4-10 zgO<3dqhYH1#-Fa}Q{pjql5>>P6gZH21zLfxZ4$SK4T@7b!|`nWF9b*84Bq8&Eht;9 z*P72x&NUCZ7*@B$`FtE=hz5b}S`|c6Ey+j@D1ZibjJaRlR;{cxAWv z?Nqa>QqV*H-*zzaPvpLMHt~nl(x6?vrPpR?zn7~wow?oj*1TKmx4j71>$hvtC$DLD zUrz0^tiP0792U&dxJxNv@r}Elsjn^aSLUu=9#mD{&9n8|ayIL$!H3s>%KEvbchBFW z%cd?VU83mGF#Dar9*s~w&AnmQRQIOvR+uWsuZ?+|a=TzApXO@q^(r%8=}iv#wCnFq z=K9}JbqU@k99Q%j-}NNk+qLCP)jXfmOO|)@?mHcnynd6({mJisP1_}u7k)|eYHXWK z63eQ)E$ufFi!3CWUY2gw%e>omCv}qEX66aH-k&35f9`Q@Us|NPetVqe8=dX*VxJdn ze`q7b=Dn(UA(2sf&g)cOmQFhNJ#<-aMELJZbA#@to>25@kbW<)&!X01 z%NMJt>1ST)tyX)h@?`DxhbgCHr>S4wv}WC&Nw-!{+Z7$2D}74QAcXTvip=M0%Tp_N zor=k`)t|ra^ySr-+(|R9mB(E=`MX#y(wSw)$!iymzB;^c*>%&^*7HxTnRga=soSZT zdDl+9s;r!v8hk6POtzBaig4pRp7eWF(<8gufvNHPu6xs-=e{;mnHzJyGKE+8L0j}; z@%8-e^UCL5HhMiR>sD3Rve&yVZ#{Q1*CO8c+qSr^Z#CN;)(X5>tGG5yUw3<+CfhaL z%bP;hZ?jvgJU67BWyiy74_)6r)_nSxttxn0`0?HE^5(uydHVgP+HE$V?Lv)Leti43 zWA|;f-RqX``95>)^P-fw!Vi{3KNsII-*5f){gdxqd%gVdB1sOBNe=nEW%;i~g_P8J w!5uhoe-Jcg1nPN%MiEAtgE$;km@@t6ukO)1^!cY^83Pb_y85}Sb4q9e0FIsP9{>OV literal 0 HcmV?d00001 diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000000000000000000000000000000000..2f1632cfddf3d9dade342351e627a0a75609fb46 GIT binary patch literal 2218 zcmV;b2vzrqP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuE6iGxuRCodHTWf3-RTMruyW6Fu zQYeUM04eX6D5c0FCjKKPrco1(K`<0SL=crI{PC3-^hZU0kQie$gh-5!7z6SH6Q0J% zqot*`H1q{R5fHFYS}dje@;kG=v$L0(yY0?wY2%*c?A&{2?!D*x?m71{of2gv!$5|C z3>qG_BW}7K_yUcT3A5C6QD<+{aq?x;MAUyAiJn#Jv8_zZtQ{P zTRzbL3U9!qVuZzS$xKU10KiW~Bgdcv1-!uAhQxf3a7q+dU6lj?yoO4Lq4TUN4}h{N z*fIM=SS8|C2$(T>w$`t@3Tka!(r!7W`x z-isCVgQD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5^Dc=k7F7?nm3nIw$NVmU-+R>> zyqOR$-2SDpJ}Pt;^RkJytDVXNTsu|mI1`~G7yw`EJR?VkGfNdqK9^^8P`JdtTV&tX4CNcV4 z&N06nZa??Fw1AgQOUSE2AmPE@WO(Fvo`%m`cDgiv(fAeRA%3AGXUbsGw{7Q`cY;1BI#ac3iN$$Hw z0LT0;xc%=q)me?Y*$xI@GRAw?+}>=9D+KTk??-HJ4=A>`V&vKFS75@MKdSF1JTq{S zc1!^8?YA|t+uKigaq!sT;Z!&0F2=k7F0PIU;F$leJLaw2UI6FL^w}OG&!;+b%ya1c z1n+6-inU<0VM-Y_s5iTElq)ThyF?StVcebpGI znw#+zLx2@ah{$_2jn+@}(zJZ{+}_N9BM;z)0yr|gF-4=Iyu@hI*Lk=-A8f#bAzc9f z`Kd6K--x@t04swJVC3JK1cHY-Hq+=|PN-VO;?^_C#;coU6TDP7Bt`;{JTG;!+jj(` zw5cLQ-(Cz-Tlb`A^w7|R56Ce;Wmr0)$KWOUZ6ai0PhzPeHwdl0H(etP zUV`va_i0s-4#DkNM8lUlqI7>YQLf)(lz9Q3Uw`)nc(z3{m5ZE77Ul$V%m)E}3&8L0 z-XaU|eB~Is08eORPk;=<>!1w)Kf}FOVS2l&9~A+@R#koFJ$Czd%Y(ENTV&A~U(IPI z;UY+gf+&6ioZ=roly<0Yst8ck>(M=S?B-ys3mLdM&)ex!hbt+ol|T6CTS+Sc0jv(& z7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBIo*981BU%%U z@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M2 z@O~lQ0OiKQp}o9I;?uxCgYVV?FH|?Riri*U$Zi_`V2eiA>l zdSm6;SEm6#T+SpcE8Ro_f2AwxzI z44hfe^WE3!h@W3RDyA_H440cpmYkv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2kVYr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/quill_native_bridge/example/macos/Runner/Configs/AppInfo.xcconfig b/quill_native_bridge/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 000000000..679d82fe5 --- /dev/null +++ b/quill_native_bridge/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = quill_native_bridge_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = dev.flutterquill.quillNativeBridgeExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Š 2024 dev.flutterquill. All rights reserved. diff --git a/quill_native_bridge/example/macos/Runner/Configs/Debug.xcconfig b/quill_native_bridge/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 000000000..36b0fd946 --- /dev/null +++ b/quill_native_bridge/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/quill_native_bridge/example/macos/Runner/Configs/Release.xcconfig b/quill_native_bridge/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 000000000..dff4f4956 --- /dev/null +++ b/quill_native_bridge/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/quill_native_bridge/example/macos/Runner/Configs/Warnings.xcconfig b/quill_native_bridge/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 000000000..42bcbf478 --- /dev/null +++ b/quill_native_bridge/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/quill_native_bridge/example/macos/Runner/DebugProfile.entitlements b/quill_native_bridge/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 000000000..dddb8a30c --- /dev/null +++ b/quill_native_bridge/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/quill_native_bridge/example/macos/Runner/Info.plist b/quill_native_bridge/example/macos/Runner/Info.plist new file mode 100644 index 000000000..4789daa6a --- /dev/null +++ b/quill_native_bridge/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/quill_native_bridge/example/macos/Runner/MainFlutterWindow.swift b/quill_native_bridge/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 000000000..3cc05eb23 --- /dev/null +++ b/quill_native_bridge/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/quill_native_bridge/example/macos/Runner/Release.entitlements b/quill_native_bridge/example/macos/Runner/Release.entitlements new file mode 100644 index 000000000..852fa1a47 --- /dev/null +++ b/quill_native_bridge/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/lib/quill_native_bridge.dart index 48815dae8..b390d862f 100644 --- a/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/lib/quill_native_bridge.dart @@ -1,5 +1,7 @@ library; +import 'package:flutter/foundation.dart'; + import 'src/quill_native_bridge_platform_interface.dart'; class QuillNativeBridge { @@ -12,6 +14,18 @@ class QuillNativeBridge { static Future isIOSSimulator() => QuillNativeBridgePlatform.instance.isIOSSimulator(); + /// Experimental and might removed in future releases. + /// + /// For now we do plan on removing this property once all non-web platforms + /// are supported. + /// + /// Available to avoid hardcoding. + static const Set supportedHtmlClipboardPlatforms = { + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS + }; + /// Return the clipboard content as HTML for **non-web platforms**. /// /// Doesn't support web, should use @@ -23,7 +37,7 @@ class QuillNativeBridge { /// Returns `null` if the HTML content is not available or if the user has not granted /// permission for pasting (on some platforms such as iOS). /// - /// Currently only supports **Android** and **iOS**. + /// Currently only supports **Android**, **iOS** and **macOS**. static Future getClipboardHTML() => QuillNativeBridgePlatform.instance.getClipboardHTML(); } diff --git a/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart index e6c5ed193..16c491c41 100644 --- a/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart' show MethodChannel; +import '../quill_native_bridge.dart'; import 'quill_native_bridge_platform_interface.dart'; class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { @@ -38,10 +39,10 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { 'getClipboardHTML() method should be only called on non-web platforms.', ); } - const supportedPlatforms = {TargetPlatform.android, TargetPlatform.iOS}; - if (!supportedPlatforms.contains(defaultTargetPlatform)) { + if (!QuillNativeBridge.supportedHtmlClipboardPlatforms + .contains(defaultTargetPlatform)) { throw FlutterError( - 'getClipboardHTML() currently only supports Android and iOS.', + 'getClipboardHTML() currently only supports Android, iOS and macOS.', ); } return true; diff --git a/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift new file mode 100644 index 000000000..14b2977ee --- /dev/null +++ b/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift @@ -0,0 +1,25 @@ +import Cocoa +import FlutterMacOS + +public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "quill_native_bridge", binaryMessenger: registrar.messenger) + let instance = QuillNativeBridgePlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "getClipboardHTML": + let pasteboard = NSPasteboard.general + if let htmlData = pasteboard.data(forType: .html) { + let html = String(data: htmlData, encoding: .utf8) + result(html) + } else { + result(nil) + } + default: + result(FlutterMethodNotImplemented) + } + } +} diff --git a/quill_native_bridge/macos/quill_native_bridge.podspec b/quill_native_bridge/macos/quill_native_bridge.podspec new file mode 100644 index 000000000..75681a6ce --- /dev/null +++ b/quill_native_bridge/macos/quill_native_bridge.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint quill_native_bridge.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'quill_native_bridge' + s.version = '0.0.1' + s.summary = 'A plugin for flutter_quill' + s.description = <<-DESC +An internal plugin for flutter_quill package to access platform-specific APIs. + DESC + s.homepage = 'https://github.com/singerdmx/flutter-quill' + s.license = { :file => '../LICENSE' } + s.author = { 'Flutter Quill' => 'https://github.com/singerdmx/flutter-quill' } + + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'FlutterMacOS' + + s.platform = :osx, '10.11' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + s.swift_version = '5.0' +end diff --git a/quill_native_bridge/pubspec.yaml b/quill_native_bridge/pubspec.yaml index d0c472d6c..a465441b9 100644 --- a/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/pubspec.yaml @@ -27,4 +27,6 @@ flutter: package: dev.flutterquill.quill_native_bridge pluginClass: QuillNativeBridgePlugin ios: + pluginClass: QuillNativeBridgePlugin + macos: pluginClass: QuillNativeBridgePlugin \ No newline at end of file From f71f57a9b862af121f1e8b0a1413a894bf799f95 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 16 Sep 2024 00:23:09 +0300 Subject: [PATCH 05/90] chore(example): clear todos in app module build.gradle --- quill_native_bridge/example/android/app/build.gradle | 5 ----- 1 file changed, 5 deletions(-) diff --git a/quill_native_bridge/example/android/app/build.gradle b/quill_native_bridge/example/android/app/build.gradle index a8cb144d9..f69a5f2e2 100644 --- a/quill_native_bridge/example/android/app/build.gradle +++ b/quill_native_bridge/example/android/app/build.gradle @@ -20,10 +20,7 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "dev.flutterquill.quill_native_bridge_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 @@ -32,8 +29,6 @@ android { 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 } } From f9dac24a90e56ae2c933d391a715f9ff4fcc7e6c Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 16 Sep 2024 13:22:30 +0300 Subject: [PATCH 06/90] chore(android): additional check before accessing the clip data item --- .../flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt index 541d80405..16e6f47c5 100644 --- a/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt +++ b/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt @@ -33,7 +33,7 @@ class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { val clipData = clipboard.primaryClip - if (clipData == null) { + if (clipData == null || clipData.itemCount <= 0) { result.success(null) return } From ff2f918fa12d2005c477831f2d5dbaa65b7ad6e8 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 16 Sep 2024 15:18:40 +0300 Subject: [PATCH 07/90] fix(flutter_quill_extensions): unrelated bug for copying an image (needs further fixes) --- .../lib/src/editor/image/image_menu.dart | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/flutter_quill_extensions/lib/src/editor/image/image_menu.dart b/flutter_quill_extensions/lib/src/editor/image/image_menu.dart index c0efff770..545a76ff7 100644 --- a/flutter_quill_extensions/lib/src/editor/image/image_menu.dart +++ b/flutter_quill_extensions/lib/src/editor/image/image_menu.dart @@ -88,15 +88,12 @@ class ImageOptionsMenu extends StatelessWidget { title: Text(context.loc.copy), onTap: () async { final navigator = Navigator.of(context); - final imageNode = - getEmbedNode(controller, controller.selection.start).value; - final image = imageNode.value.data; controller.copiedImageUrl = ImageUrl( - image, + imageSource, getImageStyleString(controller), ); - final data = await convertImageToUint8List(image); + final data = await convertImageToUint8List(imageSource); final clipboard = SystemClipboard.instance; if (data != null) { final item = DataWriterItem()..add(Formats.png(data)); From 344eea64433ae92464e0bce739526faea9465b64 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 16 Sep 2024 17:08:33 +0300 Subject: [PATCH 08/90] feat: add copyImageToClipboard() method channel, an asset file in the example, set supported platforms in pubspec.yaml, configure quill_native_bridge example --- .../clipboard/default_clipboard_service.dart | 2 +- .../QuillNativeBridgePlugin.kt | 78 +++++++++++++++++- .../android/app/src/main/AndroidManifest.xml | 10 +++ .../app/src/main/res/xml/file_paths.xml | 3 + .../example/assets/flutter-quill.png | Bin 0 -> 24711 bytes quill_native_bridge/example/lib/main.dart | 32 ++++++- quill_native_bridge/example/pubspec.yaml | 4 +- .../ios/Classes/QuillNativeBridgePlugin.swift | 11 +++ .../lib/quill_native_bridge.dart | 19 ++++- .../quill_native_bridge_method_channel.dart | 42 +++++++++- ...uill_native_bridge_platform_interface.dart | 9 +- .../Classes/QuillNativeBridgePlugin.swift | 13 +++ quill_native_bridge/pubspec.yaml | 5 ++ .../test/quill_native_bridge_test.dart | 20 +++++ 14 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 quill_native_bridge/example/android/app/src/main/res/xml/file_paths.xml create mode 100644 quill_native_bridge/example/assets/flutter-quill.png diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart index 79c884f74..74bae0811 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart @@ -9,7 +9,7 @@ import 'clipboard_service.dart'; class DefaultClipboardService implements ClipboardService { @override Future canProvideHtmlText() async => - QuillNativeBridge.supportedHtmlClipboardPlatforms + QuillNativeBridge.supportedClipboardPlatforms .contains(defaultTargetPlatform); @override diff --git a/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt index 16e6f47c5..59c75adb7 100644 --- a/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt +++ b/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt @@ -1,14 +1,18 @@ package dev.flutterquill.quill_native_bridge +import android.content.ClipData import android.content.ClipDescription import android.content.ClipboardManager import android.content.Context - +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import androidx.core.content.FileProvider import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result +import java.io.File class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { private lateinit var channel: MethodChannel @@ -60,6 +64,78 @@ class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { result.success(htmlText) } + "copyImageToClipboard" -> { + val imageBytes = call.arguments as? ByteArray + if (imageBytes == null) { + result.error( + "IMAGE_BYTES_REQUIRED", + "Image bytes are required to copy the image to the clipboard.", + null, + ) + return + } + + val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + if (bitmap == null) { + result.error( + "INVALID_IMAGE", + "The provided image bytes are invalid. Image could not be decoded.", + null + ) + return + } + + val tempFile = File(context.cacheDir, "temp_clipboard_image.png") + + try { + tempFile.outputStream().use { outputStream -> + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + } + } catch (e: Exception) { + result.error( + "COULD_NOT_SAVE_TEMP_FILE", + "Unknown error while compressing and saving the temporary image file: ${e.message}", + e.toString() + ) + return + } + + val authority = "${context.packageName}.fileprovider" + + val imageUri = try { + FileProvider.getUriForFile( + context, + authority, + tempFile, + ) + } catch (e: IllegalArgumentException) { + result.error( + "ANDROID_MANIFEST_NOT_CONFIGURED", + "You need to configure your AndroidManifest.xml file " + + "to register the provider with the meta-data with authority " + + authority, + e.toString(), + ) + return + } + + try { + val clipboard = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newUri(context.contentResolver, "Image", imageUri) + clipboard.setPrimaryClip(clip) + } catch (e: Exception) { + result.error( + "COULD_NOT_COPY_IMAGE_TO_CLIPBOARD", + "Unknown error while copying the image to the clipboard: ${e.message}", + e.toString() + ) + return + } + + result.success(null) + } + else -> result.notImplemented() } } diff --git a/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml b/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml index 4ac0b4859..551061239 100644 --- a/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml +++ b/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml @@ -30,6 +30,16 @@ + + + + + + + + + + + + + + + + + + + + quill_native_bridge_example + + + + + + diff --git a/quill_native_bridge/example/web/manifest.json b/quill_native_bridge/example/web/manifest.json new file mode 100644 index 000000000..7cc66ea86 --- /dev/null +++ b/quill_native_bridge/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "quill_native_bridge_example", + "short_name": "quill_native_bridge_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "Demonstrates how to use the quill_native_bridge plugin.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/lib/quill_native_bridge.dart index 613be6951..fc3aab01d 100644 --- a/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/lib/quill_native_bridge.dart @@ -1,6 +1,7 @@ library; -import 'package:flutter/foundation.dart' show TargetPlatform, Uint8List; +import 'package:flutter/foundation.dart' + show TargetPlatform, Uint8List, defaultTargetPlatform, kIsWeb; import 'src/quill_native_bridge_platform_interface.dart'; @@ -16,7 +17,7 @@ class QuillNativeBridge { static Future isIOSSimulator() => QuillNativeBridgePlatform.instance.isIOSSimulator(); - /// Experimental and might removed in future releases. + /// **Experimental** and might removed in future releases. /// /// For now we do plan on removing this property once all non-web platforms /// are supported. @@ -43,14 +44,20 @@ class QuillNativeBridge { static Future getClipboardHTML() => QuillNativeBridgePlatform.instance.getClipboardHTML(); - /// Copy the [imageBytes] to Clipboard to be pasted on other apps for **non-web platforms**. + /// **Experimental** and might removed in future releases. + /// + /// Available to avoid hardcoding. + static bool get isCopyingImageToClipboardSupported => + kIsWeb || supportedClipboardPlatforms.contains(defaultTargetPlatform); + + /// Copy the [imageBytes] to Clipboard to be pasted on other apps. /// /// Require modifying `AndroidManifest.xml` to work on **Android**. /// Otherwise, you will get a warning available only on debug-builds. /// See: https://github.com/singerdmx/flutter-quill#-platform-specific-configurations /// /// - /// Currently only supports **Android**, **iOS** and **macOS**. + /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. static Future copyImageToClipboard(Uint8List imageBytes) => QuillNativeBridgePlatform.instance.copyImageToClipboard(imageBytes); } diff --git a/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart index 9b68de78e..4e503ff11 100644 --- a/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart @@ -55,16 +55,9 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { @override Future copyImageToClipboard(Uint8List imageBytes) async { assert(() { - // TODO: Update this check later - if (kIsWeb) { - throw FlutterError( - 'copyImageToClipboard() method should be only called on non-web platforms.', - ); - } - if (!QuillNativeBridge.supportedClipboardPlatforms - .contains(defaultTargetPlatform)) { + if (!QuillNativeBridge.isCopyingImageToClipboardSupported) { throw FlutterError( - 'copyImageToClipboard() currently only supports Android, iOS and macOS.', + 'copyImageToClipboard() currently only supports Android, iOS, macOS and Web.', ); } return true; diff --git a/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart b/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart index 2c0723d3e..0fd0c7d75 100644 --- a/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart @@ -33,7 +33,7 @@ abstract class QuillNativeBridgePlatform extends PlatformInterface { Future getClipboardHTML() => throw UnimplementedError('getClipboardHTML() has not been implemented.'); - /// Copy the [imageBytes] to Clipboard to be pasted on other apps for **non-web platforms**. + /// Copy the [imageBytes] to Clipboard to be pasted on other apps. Future copyImageToClipboard(Uint8List imageBytes) => throw UnimplementedError( 'copyImageToClipboard() has not been implemented.', diff --git a/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart b/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart new file mode 100644 index 000000000..20a3b4dd8 --- /dev/null +++ b/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart @@ -0,0 +1,64 @@ +// In order to *not* need this ignore, consider extracting the "web" version +// of your plugin as a separate package, instead of inlining it in the same +// package as the core of your plugin. +// ignore: avoid_web_libraries_in_flutter + +// This file is referenced by pubspec.yaml. If you plan on moving this file +// Make sure to update pubspec.yaml to the new location. + +import 'dart:js_interop'; + +import 'package:flutter/foundation.dart' show Uint8List; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:web/web.dart'; + +import '../quill_native_bridge_platform_interface.dart'; + +/// A web implementation of the [QuillNativeBridgePlatform]. +/// +/// **Experimental** and can be removed. +/// +/// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: +/// +/// ```console +/// Assertion failed: "Platform interfaces must not be implemented with `implements`" +/// ``` +class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { + QuillNativeBridgeWeb._(); + + static void registerWith(Registrar registrar) { + QuillNativeBridgePlatform.instance = QuillNativeBridgeWeb._(); + } + + @override + Future copyImageToClipboard(Uint8List imageBytes) async { + final blob = Blob( + [imageBytes.toJS].jsify() as JSArray, + BlobPropertyBag(type: 'image/png'), + ); + + final clipboardItem = ClipboardItem( + {'image/png': blob}.jsify() as JSObject, + ); + + await window.navigator.clipboard + .write([clipboardItem].jsify() as ClipboardItems) + .toDart; + } + + // TODO: This web implementation doesn't work on firefox. + // Related: https://github.com/singerdmx/flutter-quill/issues/2220 + + // @override + // Future getClipboardHTML() async { + // final clipboardData = + // (await window.navigator.clipboard.read().toDart).toDart; + // for (final item in clipboardData) { + // if (item.types.toDart.contains('text/html'.toJS)) { + // final html = await item.getType('text/html').toDart; + // return (await html.text().toDart).toDart; + // } + // } + // return null; + // } +} diff --git a/quill_native_bridge/pubspec.yaml b/quill_native_bridge/pubspec.yaml index 86e540f6d..cd8464fa9 100644 --- a/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/pubspec.yaml @@ -20,6 +20,11 @@ dependencies: sdk: flutter plugin_platform_interface: ^2.1.8 + # For web only + flutter_web_plugins: + sdk: flutter + web: ^1.0.0 + dev_dependencies: flutter_test: sdk: flutter @@ -34,4 +39,7 @@ flutter: ios: pluginClass: QuillNativeBridgePlugin macos: - pluginClass: QuillNativeBridgePlugin \ No newline at end of file + pluginClass: QuillNativeBridgePlugin + web: + pluginClass: QuillNativeBridgeWeb + fileName: src/web/quill_native_bridge_web.dart \ No newline at end of file From 9840745bf366d28b56135e22961647f4c24c6b60 Mon Sep 17 00:00:00 2001 From: Ellet Date: Tue, 17 Sep 2024 19:34:51 +0300 Subject: [PATCH 13/90] feat: (WIP) retriving image file from clipboard, simplify ClipboardService, slightly update Android, disable copyImageToClipboard() for web (temporarily). WIP --- .../clipboard/super_clipboard_service.dart | 117 +++++----------- lib/extensions.dart | 1 + .../quill_controller_rich_paste.dart | 18 +-- .../editor/raw_editor/raw_editor_state.dart | 8 +- .../clipboard/clipboard_service.dart | 53 +++---- .../clipboard/default_clipboard_service.dart | 108 +++----------- lib/src/toolbar/buttons/clipboard_button.dart | 2 +- .../QuillNativeBridgePlugin.kt | 132 ++++++++++++++++-- quill_native_bridge/example/lib/main.dart | 70 ++++++++-- .../ios/Classes/QuillNativeBridgePlugin.swift | 4 + .../lib/quill_native_bridge.dart | 14 +- .../quill_native_bridge_method_channel.dart | 13 +- ...uill_native_bridge_platform_interface.dart | 4 + .../lib/src/web/quill_native_bridge_web.dart | 5 + .../Classes/QuillNativeBridgePlugin.swift | 46 ++++-- .../test/quill_native_bridge_test.dart | 12 ++ 16 files changed, 356 insertions(+), 251 deletions(-) diff --git a/flutter_quill_extensions/lib/src/editor_toolbar_controller_shared/clipboard/super_clipboard_service.dart b/flutter_quill_extensions/lib/src/editor_toolbar_controller_shared/clipboard/super_clipboard_service.dart index 1f7d0c502..3caa59a66 100644 --- a/flutter_quill_extensions/lib/src/editor_toolbar_controller_shared/clipboard/super_clipboard_service.dart +++ b/flutter_quill_extensions/lib/src/editor_toolbar_controller_shared/clipboard/super_clipboard_service.dart @@ -2,14 +2,13 @@ import 'dart:async' show Completer; import 'dart:convert' show utf8; import 'package:flutter/foundation.dart'; -// ignore: implementation_imports -import 'package:flutter_quill/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart'; +import 'package:flutter_quill/extensions.dart' show ClipboardService; import 'package:super_clipboard/super_clipboard.dart'; -/// Implementation based on https://pub.dev/packages/super_clipboard -class SuperClipboardService implements ClipboardService { - /// Null if the Clipboard API is not supported on this platform +/// Implementation using the https://pub.dev/packages/super_clipboard plugin. +class SuperClipboardService extends ClipboardService { + /// [Null] if the Clipboard API is not supported on this platform /// https://pub.dev/packages/super_clipboard#usage SystemClipboard? _getSuperClipboard() { return SystemClipboard.instance; @@ -74,99 +73,63 @@ class SuperClipboardService implements ClipboardService { } @override - Future canProvideHtmlText() { - return _canProvide(format: Formats.htmlText); - } - - @override - Future getHtmlText() { + Future getHtmlText() async { + if (!(await _canProvide(format: Formats.htmlText))) { + return null; + } return _provideSimpleValueFormatAsString(format: Formats.htmlText); } @override - Future canProvideHtmlTextFromFile() { - return _canProvide(format: Formats.htmlFile); - } - - @override - Future getHtmlTextFromFile() { - return _provideFileAsString(format: Formats.htmlFile); - } - - @override - Future canProvideMarkdownText() async { - // Formats.markdownText or Formats.mdText does not exist yet in super_clipboard - return false; - } - - @override - Future getMarkdownText() async { - // Formats.markdownText or Formats.mdText does not exist yet in super_clipboard - throw UnsupportedError( - 'SuperClipboardService does not support retrieving image files.', - ); - } - - @override - Future canProvideMarkdownTextFromFile() async { - // Formats.md is for markdown files - return _canProvide(format: Formats.md); - } - - @override - Future getMarkdownTextFromFile() async { - // Formats.md is for markdown files - return _provideFileAsString(format: Formats.md); - } - - @override - Future canProvidePlainText() { - return _canProvide(format: Formats.plainText); + Future getHtmlFile() async { + if (!(await _canProvide(format: Formats.htmlFile))) { + return null; + } + return await _provideFileAsString(format: Formats.htmlFile); } @override - Future getPlainText() { - return _provideSimpleValueFormatAsString(format: Formats.plainText); + Future getGifFile() async { + if (!(await _canProvide(format: Formats.gif))) { + return null; + } + return await _provideFileAsBytes(format: Formats.gif); } - /// This will need to be updated if [getImageFileAsBytes] updated. - /// Notice that even if the copied image is JPEG, it still can be provided - /// as PNG, will handle JPEG check in case this info is incorrect. @override - Future canProvideImageFile() async { + Future getImageFile() async { final canProvidePngFile = await _canProvide(format: Formats.png); if (canProvidePngFile) { - return true; + return _provideFileAsBytes(format: Formats.png); } final canProvideJpegFile = await _canProvide(format: Formats.jpeg); if (canProvideJpegFile) { - return true; + return _provideFileAsBytes(format: Formats.jpeg); } - return false; + return null; } - /// This will need to be updated if [canProvideImageFile] updated. @override - Future getImageFileAsBytes() async { - final canProvidePngFile = await _canProvide(format: Formats.png); - if (canProvidePngFile) { - return _provideFileAsBytes(format: Formats.png); + Future getMarkdownFile() async { + // Formats.md is for markdown files + if (!(await _canProvide(format: Formats.md))) { + return null; } - return _provideFileAsBytes(format: Formats.jpeg); + return await _provideFileAsString(format: Formats.md); } @override - Future canProvideGifFile() { - return _canProvide(format: Formats.gif); - } - - @override - Future getGifFileAsBytes() { - return _provideFileAsBytes(format: Formats.gif); + Future copyImageToClipboard(Uint8List imageBytes) async { + final clipboard = SystemClipboard.instance; + if (clipboard == null) { + return; + } + final item = DataWriterItem()..add(Formats.png(imageBytes)); + await clipboard.write([item]); } @override - Future canPaste() async { + Future get hasClipboardContent async { final clipboard = _getSuperClipboard(); if (clipboard == null) { return false; @@ -175,14 +138,4 @@ class SuperClipboardService implements ClipboardService { final availablePlatformFormats = reader.platformFormats; return availablePlatformFormats.isNotEmpty; } - - @override - Future copyImageToClipboard(Uint8List imageBytes) async { - final clipboard = SystemClipboard.instance; - if (clipboard == null) { - return; - } - final item = DataWriterItem()..add(Formats.png(imageBytes)); - await clipboard.write([item]); - } } diff --git a/lib/extensions.dart b/lib/extensions.dart index 9fe739b6b..51318e528 100644 --- a/lib/extensions.dart +++ b/lib/extensions.dart @@ -8,6 +8,7 @@ export 'src/common/utils/platform.dart'; export 'src/common/utils/string.dart'; export 'src/common/utils/widgets.dart'; export 'src/document/nodes/leaf.dart'; +export 'src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart'; export 'src/editor_toolbar_controller_shared/clipboard/clipboard_service_provider.dart'; export 'src/rules/delete.dart'; export 'src/rules/format.dart'; diff --git a/lib/src/controller/quill_controller_rich_paste.dart b/lib/src/controller/quill_controller_rich_paste.dart index ed1ca318c..486516bed 100644 --- a/lib/src/controller/quill_controller_rich_paste.dart +++ b/lib/src/controller/quill_controller_rich_paste.dart @@ -24,11 +24,13 @@ extension QuillControllerRichPaste on QuillController { if (html != null) { return html; } - if (await clipboardService.canProvideHtmlTextFromFile()) { - return await clipboardService.getHtmlTextFromFile(); + final clipboardHtmlText = await clipboardService.getHtmlText(); + if (clipboardHtmlText != null) { + return clipboardHtmlText; } - if (await clipboardService.canProvideHtmlText()) { - return await clipboardService.getHtmlText(); + final clipboardHtmlFile = await clipboardService.getHtmlFile(); + if (clipboardHtmlFile != null) { + return clipboardHtmlFile; } return null; } @@ -61,11 +63,9 @@ extension QuillControllerRichPaste on QuillController { if (markdown != null) { return markdown; } - if (await clipboardService.canProvideMarkdownTextFromFile()) { - return await clipboardService.getMarkdownTextFromFile(); - } - if (await clipboardService.canProvideMarkdownText()) { - return await clipboardService.getMarkdownText(); + final clipboardMarkdownFile = await clipboardService.getMarkdownFile(); + if (clipboardMarkdownFile != null) { + return clipboardMarkdownFile; } return null; } diff --git a/lib/src/editor/raw_editor/raw_editor_state.dart b/lib/src/editor/raw_editor/raw_editor_state.dart index 9eba64052..fc1086117 100644 --- a/lib/src/editor/raw_editor/raw_editor_state.dart +++ b/lib/src/editor/raw_editor/raw_editor_state.dart @@ -153,8 +153,8 @@ class QuillRawEditorState extends EditorState final onImagePaste = widget.configurations.onImagePaste; if (onImagePaste != null) { - if (await clipboardService.canProvideImageFile()) { - final imageBytes = await clipboardService.getImageFileAsBytes(); + final imageBytes = await clipboardService.getImageFile(); + if (imageBytes != null) { final imageUrl = await onImagePaste(imageBytes); if (imageUrl == null) { return; @@ -171,8 +171,8 @@ class QuillRawEditorState extends EditorState final onGifPaste = widget.configurations.onGifPaste; if (onGifPaste != null) { - if (await clipboardService.canProvideGifFile()) { - final gifBytes = await clipboardService.getGifFileAsBytes(); + final gifBytes = await clipboardService.getGifFile(); + if (gifBytes != null) { final gifUrl = await onGifPaste(gifBytes); if (gifUrl == null) { return; diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart index 3aae3795a..7bdad7f54 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart @@ -1,46 +1,29 @@ -import 'package:flutter/foundation.dart'; +import 'package:flutter/foundation.dart' show Uint8List; +import 'package:flutter/services.dart' show Clipboard; /// An abstraction to make it easy to provide different implementations -@immutable abstract class ClipboardService { - Future canProvideHtmlText(); - - /// Get Clipboard content as Html Text, this is platform specific and not the - /// same as [getPlainText] for two reasons: - /// 1. The user might want to paste Html text - /// 2. Copying Html text from other apps and use [getPlainText] will ignore - /// the Html content and provide it as text + /// Return HTML from the Clipboard. Future getHtmlText(); - Future canProvideHtmlTextFromFile(); - - /// Get the Html file in the Clipboard from the system - Future getHtmlTextFromFile(); - - Future canProvideMarkdownText(); - - /// Get Clipboard content as Markdown Text, this is platform specific and not the - /// same as [getPlainText] for two reasons: - /// 1. The user might want to paste Markdown text - /// 2. Copying Markdown text from other apps and use [getPlainText] will ignore - /// the Markdown content and provide it as text - Future getMarkdownText(); + /// Return HTML text file from the Clipboard. + Future getHtmlFile(); - Future canProvideMarkdownTextFromFile(); + /// Return the Markdown file in the Clipboard. + Future getMarkdownFile(); - /// Get the Markdown file in the Clipboard from the system - Future getMarkdownTextFromFile(); + /// Return image from the Clipboard. + Future getImageFile(); - Future canProvidePlainText(); - Future getPlainText(); - - Future canProvideImageFile(); - Future getImageFileAsBytes(); - - Future canProvideGifFile(); - Future getGifFileAsBytes(); - - Future canPaste(); + /// Return Gif from the Clipboard. + Future getGifFile(); + /// Copy [imageBytes] to the system clipboard to paste on other apps. Future copyImageToClipboard(Uint8List imageBytes); + + /// If the Clipboard is not empty or has something to paste + Future get hasClipboardContent async { + final clipboardData = await Clipboard.getData(Clipboard.kTextPlain); + return clipboardData != null; + } } diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart index f7d3b505e..e127403c7 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart @@ -1,109 +1,47 @@ -import 'package:flutter/foundation.dart' show debugPrint, defaultTargetPlatform; -import 'package:flutter/services.dart' show Clipboard, Uint8List; +import 'package:flutter/services.dart' show Uint8List; import 'package:quill_native_bridge/quill_native_bridge.dart' show QuillNativeBridge; import 'clipboard_service.dart'; /// Default implementation -class DefaultClipboardService implements ClipboardService { +class DefaultClipboardService extends ClipboardService { @override - Future canProvideHtmlText() async => - QuillNativeBridge.supportedClipboardPlatforms - .contains(defaultTargetPlatform); - - @override - Future getHtmlText() => QuillNativeBridge.getClipboardHTML(); - - @override - Future canProvideHtmlTextFromFile() async { - return false; - } - - @override - Future getHtmlTextFromFile() { - throw UnsupportedError( - 'DefaultClipboardService does not support retrieving HTML files.', - ); - } - - @override - Future canProvideMarkdownText() async { - return false; - } - - @override - Future getMarkdownText() { - throw UnsupportedError( - 'DefaultClipboardService does not support retrieving HTML files.', - ); - } - - @override - Future canProvideMarkdownTextFromFile() async { - return false; - } - - @override - Future getMarkdownTextFromFile() { - throw UnsupportedError( - 'DefaultClipboardService does not support retrieving Markdown text.', - ); - } - - @override - Future canProvidePlainText() async { - final plainText = (await Clipboard.getData(Clipboard.kTextPlain))?.text; - return plainText == null; - } - - @override - Future getPlainText() async { - final plainText = (await Clipboard.getData(Clipboard.kTextPlain))?.text; - return plainText; - } - - @override - Future canProvideImageFile() async { - return false; + Future getHtmlText() async { + if (!QuillNativeBridge.isClipboardOperationsSupported) { + return null; + } + return await QuillNativeBridge.getClipboardHTML(); } @override - Future getImageFileAsBytes() { - throw UnsupportedError( - 'DefaultClipboardService does not support retrieving image files.', - ); + Future getImageFile() async { + if (!QuillNativeBridge.isClipboardOperationsSupported) { + return null; + } + return await QuillNativeBridge.getClipboardImage(); } @override - Future canProvideGifFile() async { - return false; + Future copyImageToClipboard(Uint8List imageBytes) async { + if (!QuillNativeBridge.isClipboardOperationsSupported) { + return; + } + await QuillNativeBridge.copyImageToClipboard(imageBytes); } @override - Future getGifFileAsBytes() { - throw UnsupportedError( - 'DefaultClipboardService does not support retrieving GIF files.', - ); + Future getGifFile() async { + return null; } @override - Future canPaste() async { - final plainText = await getPlainText(); - return plainText != null; + Future getHtmlFile() async { + return null; } @override - Future copyImageToClipboard(Uint8List imageBytes) async { - if (!QuillNativeBridge.isCopyingImageToClipboardSupported) { - assert(() { - debugPrint( - 'Copying an image to the clipboard is currently not supported on ${defaultTargetPlatform.name}', - ); - return true; - }()); - return; - } - await QuillNativeBridge.copyImageToClipboard(imageBytes); + Future getMarkdownFile() async { + return null; } } diff --git a/lib/src/toolbar/buttons/clipboard_button.dart b/lib/src/toolbar/buttons/clipboard_button.dart index 401070f24..4f1d43bea 100644 --- a/lib/src/toolbar/buttons/clipboard_button.dart +++ b/lib/src/toolbar/buttons/clipboard_button.dart @@ -29,7 +29,7 @@ class ClipboardMonitor { Future _update(void Function() listener) async { final clipboardService = ClipboardServiceProvider.instance; - if (await clipboardService.canPaste()) { + if (await clipboardService.hasClipboardContent) { _canPaste = true; listener(); } diff --git a/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt index 59c75adb7..d8fe6a48b 100644 --- a/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt +++ b/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt @@ -6,12 +6,18 @@ import android.content.ClipboardManager import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.ImageDecoder +import android.net.Uri +import android.os.Build import androidx.core.content.FileProvider +import androidx.core.graphics.decodeBitmap +import androidx.core.net.toFile import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result +import java.io.ByteArrayOutputStream import java.io.File class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { @@ -75,21 +81,47 @@ class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { return } - val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) - if (bitmap == null) { - result.error( - "INVALID_IMAGE", - "The provided image bytes are invalid. Image could not be decoded.", - null - ) - return + val bitmap: Bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // Api 29 and above + try { + ImageDecoder.createSource(imageBytes).decodeBitmap { _, _ -> } + } catch (e: Exception) { + result.error( + "INVALID_IMAGE", + "The provided image bytes are invalid, image could not be decoded: ${e.message}", + e.toString(), + ) + return + } + } else { + // Backward compatibility with older versions + val bitmap: Bitmap? = + BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + if (bitmap == null) { + result.error( + "INVALID_IMAGE", + "The provided image bytes are invalid. Image could not be decoded.", + null + ) + return + } + bitmap } val tempFile = File(context.cacheDir, "temp_clipboard_image.png") try { tempFile.outputStream().use { outputStream -> - bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + val compressedSuccessfully = + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + if (!compressedSuccessfully) { + result.error( + "COULD_NOT_COMPRESS_IMAGE", + "Unknown error while compressing the image", + null, + ) + return + } } } catch (e: Exception) { result.error( @@ -136,6 +168,88 @@ class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { result.success(null) } + "getClipboardImage" -> { + val clipboard = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + + if (!clipboard.hasPrimaryClip()) { + result.success(null) + return + } + + val clipData = clipboard.primaryClip + + if (clipData == null || clipData.itemCount <= 0) { + result.success(null) + return + } + + val clipboardItem = clipData.getItemAt(0) + + fun getImageUriFromClipboard(): Uri? { + val imageUri = clipboardItem.uri + if (imageUri == null || !clipData.description.hasMimeType("image/*")) { + // Optional: Check if the clipboard item contains text that might be a file path + val text = clipboardItem.text ?: return null + if (text.startsWith("file://")) { + val fileUri = Uri.parse(text.toString()) + return try { + fileUri + } catch (e: Exception) { + e.printStackTrace() + null + } + } + } + return imageUri + } + + val imageUri: Uri? = getImageUriFromClipboard() + if (imageUri == null) { + result.success(null) + return + } + + val bitmap = try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // Api 29 and above + val source = ImageDecoder.createSource(context.contentResolver, imageUri) + source.decodeBitmap { _, _ -> } + } else { + // Backward compatibility with older versions + checkNotNull(context.contentResolver.openInputStream(imageUri)) { + "Input stream is null, the provider might have recently crashed." + }.use { inputStream -> + val bitmap: Bitmap? = BitmapFactory.decodeStream(inputStream) + checkNotNull(bitmap) { "The image could not be decoded" } + bitmap + } + } + } catch (e: Exception) { + result.error( + "COULD_NOT_DECODE_IMAGE", + "Could not decode bitmap from Uri: ${e.message}", + e.toString(), + ) + return + } + + val imageBytes = ByteArrayOutputStream().use { outputStream -> + val compressedSuccessfully = + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + if (!compressedSuccessfully) { + result.error( + "COULD_NOT_COMPRESS_IMAGE", + "Unknown error while compressing the image", + null, + ) + return + } + outputStream.toByteArray() + } + result.success(imageBytes) + } + else -> result.notImplemented() } } diff --git a/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/example/lib/main.dart index 1d9c98b9e..0abce1585 100644 --- a/quill_native_bridge/example/lib/main.dart +++ b/quill_native_bridge/example/lib/main.dart @@ -8,6 +8,8 @@ void main() { runApp(const MyApp()); } +const _flutterQuillAssetImage = 'assets/flutter-quill.png'; + class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -34,6 +36,11 @@ class Buttons extends StatelessWidget { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ + Image.asset( + _flutterQuillAssetImage, + width: 300, + ), + const SizedBox(height: 50), ElevatedButton( onPressed: () async { final scaffoldMessenger = ScaffoldMessenger.of(context); @@ -65,8 +72,7 @@ class Buttons extends StatelessWidget { ); return; } - if (!QuillNativeBridge.supportedClipboardPlatforms - .contains(defaultTargetPlatform)) { + if (!QuillNativeBridge.isClipboardOperationsSupported) { scaffoldMessenger.showText( 'Currently, this functionality is only supported on Android, iOS and macOS.', ); @@ -90,23 +96,71 @@ class Buttons extends StatelessWidget { ElevatedButton( onPressed: () async { final scaffoldMessenger = ScaffoldMessenger.of(context); - if (!QuillNativeBridge.isCopyingImageToClipboardSupported) { + if (!QuillNativeBridge.isClipboardOperationsSupported) { scaffoldMessenger.showText( 'Currently, this functionality is only supported on Android, iOS, macOS and Web.', ); return; } - final imageBytes = - (await rootBundle.load('assets/flutter-quill.png')) - .buffer - .asUint8List(); + final imageBytes = (await rootBundle.load(_flutterQuillAssetImage)) + .buffer + .asUint8List(); await QuillNativeBridge.copyImageToClipboard(imageBytes); + + // Not widely supported but some apps copy the image as a text: + // final file = File( + // '${Directory.systemTemp.path}/clipboard-image.png', + // ); + // await file.create(recursive: true); + // await file.writeAsBytes(imageBytes); + // Clipboard.setData( + // ClipboardData( + // // Currently the Android plugin doesn't support content:// + // text: 'file://${file.absolute.path}', + // ), + // ); + scaffoldMessenger.showText( 'Image has been copied to the clipboard.', ); }, child: const Text('Copy Image to Clipboard'), - ) + ), + ElevatedButton( + onPressed: () async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + // TODO: Update this check if web supported or not + if (kIsWeb) { + scaffoldMessenger.showText( + 'Retrieving image from the clipboard is currently not supported.', + ); + return; + } + if (!QuillNativeBridge.isClipboardOperationsSupported) { + scaffoldMessenger.showText( + 'Currently, this functionality is only supported on Android, iOS, and macOS.', + ); + return; + } + final imageBytes = await QuillNativeBridge.getClipboardImage(); + if (imageBytes == null) { + scaffoldMessenger.showText( + 'The image is not available on the clipboard.', + ); + return; + } + if (!context.mounted) { + return; + } + showDialog( + context: context, + builder: (context) => Dialog( + child: Image.memory(imageBytes), + ), + ); + }, + child: const Text('Retrive Image from Clipboard'), + ), ], ); } diff --git a/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift index 54e6c6b85..edac60fe6 100644 --- a/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift +++ b/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift @@ -35,6 +35,10 @@ public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { } else { result(FlutterError(code: "IMAGE_BYTES_REQUIRED", message: "Image bytes are required to copy the image to the clipboard.", details: nil)) } + case "getClipboardImage": + let image = UIPasteboard.general.image + let data = image?.pngData() + result(data) default: result(FlutterMethodNotImplemented) } diff --git a/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/lib/quill_native_bridge.dart index fc3aab01d..9450ab941 100644 --- a/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/lib/quill_native_bridge.dart @@ -23,7 +23,7 @@ class QuillNativeBridge { /// are supported. /// /// Available to avoid hardcoding. - static const Set supportedClipboardPlatforms = { + static const Set _supportedClipboardPlatforms = { TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS @@ -47,8 +47,8 @@ class QuillNativeBridge { /// **Experimental** and might removed in future releases. /// /// Available to avoid hardcoding. - static bool get isCopyingImageToClipboardSupported => - kIsWeb || supportedClipboardPlatforms.contains(defaultTargetPlatform); + static bool get isClipboardOperationsSupported => + _supportedClipboardPlatforms.contains(defaultTargetPlatform); /// Copy the [imageBytes] to Clipboard to be pasted on other apps. /// @@ -57,7 +57,13 @@ class QuillNativeBridge { /// See: https://github.com/singerdmx/flutter-quill#-platform-specific-configurations /// /// - /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. + /// Currently only supports **Android**, **iOS**, **macOS**. static Future copyImageToClipboard(Uint8List imageBytes) => QuillNativeBridgePlatform.instance.copyImageToClipboard(imageBytes); + + /// Return the copied image from the Clipboard. + /// + /// Currently only supports **Android**, **iOS**, **macOS**. + static Future getClipboardImage() => + QuillNativeBridgePlatform.instance.getClipboardImage(); } diff --git a/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart index 4e503ff11..69161cdcd 100644 --- a/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart @@ -39,8 +39,7 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { 'getClipboardHTML() method should be only called on non-web platforms.', ); } - if (!QuillNativeBridge.supportedClipboardPlatforms - .contains(defaultTargetPlatform)) { + if (!QuillNativeBridge.isClipboardOperationsSupported) { throw FlutterError( 'getClipboardHTML() currently only supports Android, iOS and macOS.', ); @@ -55,7 +54,7 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { @override Future copyImageToClipboard(Uint8List imageBytes) async { assert(() { - if (!QuillNativeBridge.isCopyingImageToClipboardSupported) { + if (!QuillNativeBridge.isClipboardOperationsSupported) { throw FlutterError( 'copyImageToClipboard() currently only supports Android, iOS, macOS and Web.', ); @@ -82,4 +81,12 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { rethrow; } } + + @override + Future getClipboardImage() async { + final imageBytes = await methodChannel.invokeMethod( + 'getClipboardImage', + ); + return imageBytes; + } } diff --git a/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart b/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart index 0fd0c7d75..e2437b8a6 100644 --- a/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart @@ -38,4 +38,8 @@ abstract class QuillNativeBridgePlatform extends PlatformInterface { throw UnimplementedError( 'copyImageToClipboard() has not been implemented.', ); + + /// Return the copied image from the Clipboard. + Future getClipboardImage() => + throw UnimplementedError('getClipboardImage() has not been implemented.'); } diff --git a/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart b/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart index 20a3b4dd8..e1bdde07d 100644 --- a/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart +++ b/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart @@ -22,6 +22,8 @@ import '../quill_native_bridge_platform_interface.dart'; /// /// ```console /// Assertion failed: "Platform interfaces must not be implemented with `implements`" +/// +/// See https://github.com/flutter/flutter/issues/127396 /// ``` class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { QuillNativeBridgeWeb._(); @@ -41,6 +43,7 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { {'image/png': blob}.jsify() as JSObject, ); + // TODO: Will cause issue on Firefox as Clipboard API is not supported, fallback to Clipboard events. await window.navigator.clipboard .write([clipboardItem].jsify() as ClipboardItems) .toDart; @@ -49,6 +52,8 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { // TODO: This web implementation doesn't work on firefox. // Related: https://github.com/singerdmx/flutter-quill/issues/2220 + // TODO: Need a method to check if Clipboard API is supported (which is not on Firefox) + // @override // Future getClipboardHTML() async { // final clipboardData = diff --git a/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift index 6eba13109..228e9e33f 100644 --- a/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift +++ b/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift @@ -19,18 +19,42 @@ public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { result(nil) } case "copyImageToClipboard": - if let data = call.arguments as? FlutterStandardTypedData { - if let image = NSImage(data: data.data) { - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setData(image.tiffRepresentation!, forType: .png) - result(nil) - } else { - result(FlutterError(code: "INVALID_IMAGE", message: "Unable to create NSImage from image bytes.", details: nil)) - } - } else { - result(FlutterError(code: "IMAGE_BYTES_REQUIRED", message: "Image bytes are required to copy the image to the clipboard.", details: nil)) + guard let data = call.arguments as? FlutterStandardTypedData else { + result(FlutterError(code: "IMAGE_BYTES_REQUIRED", message: "Image bytes are required to copy the image to the clipboard.", details: nil)) + return + } + + guard let image = NSImage(data: data.data) else { + result(FlutterError(code: "INVALID_IMAGE", message: "Unable to create NSImage from image bytes.", details: nil)) + return + } + + guard let tiffData = image.tiffRepresentation else { + result(FlutterError(code: "INVALID_IMAGE", message: "Unable to get TIFF representation from NSImage.", details: nil)) + return + } + + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setData(tiffData, forType: .png) + result(nil) + + case "getClipboardImage": + let pasteboard = NSPasteboard.general + + // TODO: This can return null when copying an image from other apps (e.g Telegram, Apple notes), seems to work + // with macOS screenshot and Google Chrome, fix this issue later + guard let image = pasteboard.readObjects(forClasses: [NSImage.self], options: nil)?.first as? NSImage else { + result(nil) + return } + if let tiffData = image.tiffRepresentation, + let bitmap = NSBitmapImageRep(data: tiffData), + let pngData = bitmap.representation(using: .png, properties: [:]) { + result(pngData) + } else { + result(nil) + } default: result(FlutterMethodNotImplemented) } diff --git a/quill_native_bridge/test/quill_native_bridge_test.dart b/quill_native_bridge/test/quill_native_bridge_test.dart index 0ac7f1b5d..6d4e0ff76 100644 --- a/quill_native_bridge/test/quill_native_bridge_test.dart +++ b/quill_native_bridge/test/quill_native_bridge_test.dart @@ -22,6 +22,11 @@ class MockQuillNativeBridgePlatform Future copyImageToClipboard(Uint8List imageBytes) async { imageHasCopied = true; } + + @override + Future getClipboardImage() async { + return Uint8List.fromList([0, 2, 1]); + } } void main() { @@ -58,4 +63,11 @@ void main() { true, ); }); + + test('copyImageToClipboard()', () async { + expect( + await QuillNativeBridgePlatform.instance.getClipboardImage(), + Uint8List.fromList([0, 2, 1]), + ); + }); } From 6f91fdbe3a996c4b3f984f206d0c019fc7c35a2c Mon Sep 17 00:00:00 2001 From: Ellet Date: Tue, 17 Sep 2024 21:46:55 +0300 Subject: [PATCH 14/90] chore(example): rename _flutterQuillAssetImage to _kFlutterQuillAssetImage --- quill_native_bridge/example/lib/main.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/example/lib/main.dart index 0abce1585..2b2f7b9e5 100644 --- a/quill_native_bridge/example/lib/main.dart +++ b/quill_native_bridge/example/lib/main.dart @@ -8,7 +8,7 @@ void main() { runApp(const MyApp()); } -const _flutterQuillAssetImage = 'assets/flutter-quill.png'; +const _kFlutterQuillAssetImage = 'assets/flutter-quill.png'; class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -37,7 +37,7 @@ class Buttons extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( - _flutterQuillAssetImage, + _kFlutterQuillAssetImage, width: 300, ), const SizedBox(height: 50), @@ -102,7 +102,7 @@ class Buttons extends StatelessWidget { ); return; } - final imageBytes = (await rootBundle.load(_flutterQuillAssetImage)) + final imageBytes = (await rootBundle.load(_kFlutterQuillAssetImage)) .buffer .asUint8List(); await QuillNativeBridge.copyImageToClipboard(imageBytes); From 0f9986f5bf8b6bedd0b099ed4851d5e1a65d7084 Mon Sep 17 00:00:00 2001 From: Ellet Date: Tue, 17 Sep 2024 23:39:34 +0300 Subject: [PATCH 15/90] chore(example): update error messages in quill_native_bridge example --- quill_native_bridge/example/lib/main.dart | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/example/lib/main.dart index 2b2f7b9e5..272471005 100644 --- a/quill_native_bridge/example/lib/main.dart +++ b/quill_native_bridge/example/lib/main.dart @@ -46,7 +46,7 @@ class Buttons extends StatelessWidget { final scaffoldMessenger = ScaffoldMessenger.of(context); if (kIsWeb) { scaffoldMessenger.showText( - "Can't check if the device is simulator on web.", + "Can't check if the device is iOS simulator on the web.", ); return; } @@ -68,13 +68,13 @@ class Buttons extends StatelessWidget { final scaffoldMessenger = ScaffoldMessenger.of(context); if (kIsWeb) { scaffoldMessenger.showText( - "Can't get the HTML content from Clipboard on web without paste event on web.", + 'Retriving HTML from the Clipboard is currently not supported on the web.', ); return; } if (!QuillNativeBridge.isClipboardOperationsSupported) { scaffoldMessenger.showText( - 'Currently, this functionality is only supported on Android, iOS and macOS.', + 'Getting HTML from the Clipboard is not supported on ${defaultTargetPlatform.name}', ); return; } @@ -96,9 +96,15 @@ class Buttons extends StatelessWidget { ElevatedButton( onPressed: () async { final scaffoldMessenger = ScaffoldMessenger.of(context); + if (kIsWeb) { + scaffoldMessenger.showText( + 'Copying an image to the clipboard is currently not supported on web.', + ); + return; + } if (!QuillNativeBridge.isClipboardOperationsSupported) { scaffoldMessenger.showText( - 'Currently, this functionality is only supported on Android, iOS, macOS and Web.', + 'Copying an image to the Clipboard is not supported on ${defaultTargetPlatform.name}', ); return; } @@ -129,16 +135,15 @@ class Buttons extends StatelessWidget { ElevatedButton( onPressed: () async { final scaffoldMessenger = ScaffoldMessenger.of(context); - // TODO: Update this check if web supported or not if (kIsWeb) { scaffoldMessenger.showText( - 'Retrieving image from the clipboard is currently not supported.', + 'Retriving an image from the clipboard is currently not supported on web.', ); return; } if (!QuillNativeBridge.isClipboardOperationsSupported) { scaffoldMessenger.showText( - 'Currently, this functionality is only supported on Android, iOS, and macOS.', + 'Retriving an image from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', ); return; } From 1837a37b0e6bb020b76a302befd44c377a31b76e Mon Sep 17 00:00:00 2001 From: Ellet Date: Thu, 19 Sep 2024 16:59:12 +0300 Subject: [PATCH 16/90] feat: add support for gif, fix unhandled exception on Android only when getting image from clipboard before closing the app on a new app start, cleanup the platform support check --- .../clipboard/default_clipboard_service.dart | 13 +- .../QuillNativeBridgePlugin.kt | 98 +----- .../clipboard/ClipboardImageHandler.kt | 255 +++++++++++++++ quill_native_bridge/example/lib/main.dart | 294 ++++++++++-------- .../ios/Classes/QuillNativeBridgePlugin.swift | 3 + .../lib/quill_native_bridge.dart | 29 +- .../lib/src/platform_feature.dart | 64 ++++ .../quill_native_bridge_method_channel.dart | 96 +++++- ...uill_native_bridge_platform_interface.dart | 4 + .../Classes/QuillNativeBridgePlugin.swift | 4 + .../test/quill_native_bridge_test.dart | 14 +- 11 files changed, 631 insertions(+), 243 deletions(-) create mode 100644 quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardImageHandler.kt create mode 100644 quill_native_bridge/lib/src/platform_feature.dart diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart index e127403c7..03e8ede20 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart @@ -1,6 +1,6 @@ import 'package:flutter/services.dart' show Uint8List; import 'package:quill_native_bridge/quill_native_bridge.dart' - show QuillNativeBridge; + show QuillNativeBridge, QuillNativeBridgePlatformFeature; import 'clipboard_service.dart'; @@ -8,7 +8,7 @@ import 'clipboard_service.dart'; class DefaultClipboardService extends ClipboardService { @override Future getHtmlText() async { - if (!QuillNativeBridge.isClipboardOperationsSupported) { + if (QuillNativeBridgePlatformFeature.getClipboardHTML.isUnsupported) { return null; } return await QuillNativeBridge.getClipboardHTML(); @@ -16,7 +16,7 @@ class DefaultClipboardService extends ClipboardService { @override Future getImageFile() async { - if (!QuillNativeBridge.isClipboardOperationsSupported) { + if (QuillNativeBridgePlatformFeature.getClipboardImage.isUnsupported) { return null; } return await QuillNativeBridge.getClipboardImage(); @@ -24,7 +24,7 @@ class DefaultClipboardService extends ClipboardService { @override Future copyImageToClipboard(Uint8List imageBytes) async { - if (!QuillNativeBridge.isClipboardOperationsSupported) { + if (QuillNativeBridgePlatformFeature.copyImageToClipboard.isUnsupported) { return; } await QuillNativeBridge.copyImageToClipboard(imageBytes); @@ -32,7 +32,10 @@ class DefaultClipboardService extends ClipboardService { @override Future getGifFile() async { - return null; + if (QuillNativeBridgePlatformFeature.getClipboardGif.isUnsupported) { + return null; + } + return QuillNativeBridge.getClipboardGif(); } @override diff --git a/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt index d8fe6a48b..f32497755 100644 --- a/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt +++ b/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt @@ -7,17 +7,14 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.ImageDecoder -import android.net.Uri import android.os.Build import androidx.core.content.FileProvider import androidx.core.graphics.decodeBitmap -import androidx.core.net.toFile +import dev.flutterquill.quill_native_bridge.clipboard.ClipboardImageHandler import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import io.flutter.plugin.common.MethodChannel.Result -import java.io.ByteArrayOutputStream import java.io.File class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { @@ -30,7 +27,7 @@ class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { channel.setMethodCallHandler(this) } - override fun onMethodCall(call: MethodCall, result: Result) { + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { "getClipboardHTML" -> { val clipboard = @@ -169,85 +166,20 @@ class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { } "getClipboardImage" -> { - val clipboard = - context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - - if (!clipboard.hasPrimaryClip()) { - result.success(null) - return - } - - val clipData = clipboard.primaryClip - - if (clipData == null || clipData.itemCount <= 0) { - result.success(null) - return - } - - val clipboardItem = clipData.getItemAt(0) - - fun getImageUriFromClipboard(): Uri? { - val imageUri = clipboardItem.uri - if (imageUri == null || !clipData.description.hasMimeType("image/*")) { - // Optional: Check if the clipboard item contains text that might be a file path - val text = clipboardItem.text ?: return null - if (text.startsWith("file://")) { - val fileUri = Uri.parse(text.toString()) - return try { - fileUri - } catch (e: Exception) { - e.printStackTrace() - null - } - } - } - return imageUri - } - - val imageUri: Uri? = getImageUriFromClipboard() - if (imageUri == null) { - result.success(null) - return - } - - val bitmap = try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - // Api 29 and above - val source = ImageDecoder.createSource(context.contentResolver, imageUri) - source.decodeBitmap { _, _ -> } - } else { - // Backward compatibility with older versions - checkNotNull(context.contentResolver.openInputStream(imageUri)) { - "Input stream is null, the provider might have recently crashed." - }.use { inputStream -> - val bitmap: Bitmap? = BitmapFactory.decodeStream(inputStream) - checkNotNull(bitmap) { "The image could not be decoded" } - bitmap - } - } - } catch (e: Exception) { - result.error( - "COULD_NOT_DECODE_IMAGE", - "Could not decode bitmap from Uri: ${e.message}", - e.toString(), - ) - return - } + ClipboardImageHandler.getClipboardImage( + context = context, + // Will convert the image to PNG + imageType = ClipboardImageHandler.ImageType.AnyExceptGif, + result = result, + ) + } - val imageBytes = ByteArrayOutputStream().use { outputStream -> - val compressedSuccessfully = - bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) - if (!compressedSuccessfully) { - result.error( - "COULD_NOT_COMPRESS_IMAGE", - "Unknown error while compressing the image", - null, - ) - return - } - outputStream.toByteArray() - } - result.success(imageBytes) + "getClipboardGif" -> { + ClipboardImageHandler.getClipboardImage( + context = context, + result = result, + imageType = ClipboardImageHandler.ImageType.Gif, + ) } else -> result.notImplemented() diff --git a/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardImageHandler.kt b/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardImageHandler.kt new file mode 100644 index 000000000..60f06d6b8 --- /dev/null +++ b/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardImageHandler.kt @@ -0,0 +1,255 @@ +package dev.flutterquill.quill_native_bridge.clipboard + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.ImageDecoder +import android.net.Uri +import android.os.Build +import androidx.core.graphics.decodeBitmap +import io.flutter.plugin.common.MethodChannel +import java.io.ByteArrayOutputStream +import java.io.FileNotFoundException + +// TODO: Extract what can be extracted outside of ClipboardImageHandler for other method channels + +object ClipboardImageHandler { + private const val MIME_TYPE_IMAGE_ALL = "image/*" + private const val MIME_TYPE_IMAGE_PNG = "image/png" + private const val MIME_TYPE_IMAGE_JPEG = "image/jpeg" + private const val MIME_TYPE_IMAGE_GIF = "image/gif" + + /** + * The media/image type. + * + * @property Png [MIME_TYPE_IMAGE_PNG] + * @property AnyExceptGif All images that are [MIME_TYPE_IMAGE_ALL] but not [MIME_TYPE_IMAGE_GIF] + * @property Gif [MIME_TYPE_IMAGE_GIF] + * @property Jpeg [MIME_TYPE_IMAGE_JPEG] + * */ + enum class ImageType { Png, Jpeg, AnyExceptGif, Gif } + + /** + * Read the primary clip of the system clipboard using [ClipboardManager] + * */ + private fun getPrimaryClip(context: Context): ClipData? { + val clipboard = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + + if (!clipboard.hasPrimaryClip()) { + return null + } + + val clipData = clipboard.primaryClip + + if (clipData == null || clipData.itemCount <= 0) { + return null + } + + return clipData + } + + /** + * Return Image URI from [clipData]. + * + * If the [ClipData.Item.getUri] is `null` then will check if the [clipData] + * is a text containing the file path, parse it and return the [Uri]. + * + * Should check if can read the [Uri] even if it's non null using [readOrThrow] otherwise a exception + * can arise when the app no longer have access to the [Uri]. + * + * @param clipData The clip data to extract the [Uri] from. + * @param imageType The type of the image whatever if it's png, gif or any. + * */ + private fun getImageUri( + clipData: ClipData, + imageType: ImageType, + ): Uri? { + val clipboardItem = clipData.getItemAt(0) + + val imageUri = clipboardItem.uri + val matchMimeType: Boolean = when (imageType) { + ImageType.Png -> clipData.description.hasMimeType(MIME_TYPE_IMAGE_PNG) + ImageType.Jpeg -> clipData.description.hasMimeType(MIME_TYPE_IMAGE_JPEG) + ImageType.AnyExceptGif -> clipData.description.hasMimeType(MIME_TYPE_IMAGE_ALL) && + !clipData.description.hasMimeType(MIME_TYPE_IMAGE_GIF) + + ImageType.Gif -> clipData.description.hasMimeType(MIME_TYPE_IMAGE_GIF) + } + if (imageUri == null || !matchMimeType) { + // Image URI is null or the mime type doesn't match. + // This is not widely supported but some apps do store images as file paths in a text + + // Optional: Check if the clipboard item contains text that might be a file path + val text = clipboardItem.text ?: return null + if (!text.startsWith("file://")) { + return null + } + val fileUri = Uri.parse(text.toString()) + return try { + fileUri + } catch (e: Exception) { + e.printStackTrace() + null + } + } + return imageUri + } + + /** + * A method to see if any exceptions can occur + * before start reading the file. + * + * The app can lose access to the [Uri] due to lifecycle changes. + * + * @throws SecurityException When the app loses access to the [Uri] due to app lifecycle changes + * or app restart. + * @throws FileNotFoundException Could be thrown when the [Uri] is no longer on the clipboard. + * */ + @Throws(Exception::class) + private fun Uri.readOrThrow( + context: Context, + ) = try { + context.contentResolver.openInputStream(this)?.close() + } catch (e: Exception) { + throw e + } + + /** + * Get the clipboard Image. + * */ + fun getClipboardImage( + context: Context, + imageType: ImageType, + result: MethodChannel.Result, + ) { + val primaryClipData = getPrimaryClip(context) ?: kotlin.run { + result.success(null) + return + } + val imageUri = getImageUri( + clipData = primaryClipData, + imageType = imageType, + ) + if (imageUri == null) { + result.success(null) + return + } + try { + imageUri.readOrThrow(context) + } catch (e: Exception) { + when (e) { + is SecurityException -> result.error( + "FILE_READ_PERMISSION_DENIED", + "An image exists on the clipboard, but the app no longer " + + "has permission to access it. This may be due to the app's " + + "lifecycle or a recent app restart: ${e.message}", + e.toString(), + ) + + is FileNotFoundException -> result.error( + "FILE_NOT_FOUND", + "The image file can't be found, the provided URI could not be opened: ${e.message}", + e.toString() + ) + + else -> result.error( + "UNKNOWN_ERROR_READING_FILE", + "An unknown occurred while reading the image file URI: ${e.message}", + e.toString() + ) + } + return + } + when (imageType) { + ImageType.Png, ImageType.Jpeg, + ImageType.AnyExceptGif -> getClipboardImageAsPng(context, result, imageUri) + + ImageType.Gif -> getClipboardGif(context, result, imageUri) + } + } + + /** + * Get the image from [imageUri] and then convert it to [Bitmap] to decode and compress it + * to [ImageType.Png] + * */ + private fun getClipboardImageAsPng( + context: Context, + result: MethodChannel.Result, + imageUri: Uri + ) { + val bitmap = try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // Api 29 and above (use a newer API) + val source = ImageDecoder.createSource(context.contentResolver, imageUri) + source.decodeBitmap { _, _ -> } + } else { + // Backward compatibility with older versions + checkNotNull(context.contentResolver.openInputStream(imageUri)) { + "Input stream is null, the provider might have recently crashed." + }.use { inputStream -> + val bitmap: Bitmap? = BitmapFactory.decodeStream(inputStream) + checkNotNull(bitmap) { "The image could not be decoded" } + bitmap + } + } + } catch (e: Exception) { + result.error( + "COULD_NOT_DECODE_IMAGE", + "Could not decode bitmap from Uri: ${e.message}", + e.toString(), + ) + return + } + + val imageBytes = ByteArrayOutputStream().use { outputStream -> + val compressedSuccessfully = + bitmap.compress( + Bitmap.CompressFormat.PNG, + /** + * Quality will be ignored for png images. See [Bitmap.CompressFormat.PNG] docs + * */ + 100, + outputStream + ) + if (!compressedSuccessfully) { + result.error( + "COULD_NOT_COMPRESS_IMAGE", + "Unknown error while compressing the image", + null, + ) + return + } + outputStream.toByteArray() + } + result.success(imageBytes) + } + + private fun getClipboardGif( + context: Context, + result: MethodChannel.Result, + imageUri: Uri + ) { + try { + val imageBytes = uriToByteArray(context, imageUri) + result.success(imageBytes) + } catch (e: Exception) { + result.error( + "COULD_NOT_CONVERT_URI_TO_BYTES", + "Could not convert Image URI to ByteArray: ${e.message}", + e.toString(), + ) + return + } + } + + private fun uriToByteArray(context: Context, uri: Uri): ByteArray { + return checkNotNull(context.contentResolver.openInputStream(uri)) { + "Input stream is null, the provider might have recently crashed." + }.use { inputStream -> + inputStream.readBytes() + } + } +} \ No newline at end of file diff --git a/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/example/lib/main.dart index 272471005..06f3024c2 100644 --- a/quill_native_bridge/example/lib/main.dart +++ b/quill_native_bridge/example/lib/main.dart @@ -2,7 +2,8 @@ import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show Clipboard, ClipboardData, rootBundle; -import 'package:quill_native_bridge/quill_native_bridge.dart'; +import 'package:quill_native_bridge/quill_native_bridge.dart' + show QuillNativeBridge, QuillNativeBridgePlatformFeature; void main() { runApp(const MyApp()); @@ -41,134 +42,183 @@ class Buttons extends StatelessWidget { width: 300, ), const SizedBox(height: 50), - ElevatedButton( - onPressed: () async { - final scaffoldMessenger = ScaffoldMessenger.of(context); - if (kIsWeb) { - scaffoldMessenger.showText( - "Can't check if the device is iOS simulator on the web.", - ); - return; - } - if (defaultTargetPlatform != TargetPlatform.iOS) { - scaffoldMessenger.showText( - 'Must be on iOS to check if simualtor.', - ); - return; - } - final result = await QuillNativeBridge.isIOSSimulator(); - scaffoldMessenger.showText(result - ? "You're running the app on iOS simulator" - : "You're running the app on real iOS device."); - }, - child: const Text('Is iOS Simulator'), + ElevatedButton.icon( + onPressed: () => _onButtonClick( + QuillNativeBridgePlatformFeature.isIOSSimulator, + context: context, + ), + label: const Text('Is iOS Simulator'), + icon: const Icon(Icons.apple), ), - ElevatedButton( - onPressed: () async { - final scaffoldMessenger = ScaffoldMessenger.of(context); - if (kIsWeb) { - scaffoldMessenger.showText( - 'Retriving HTML from the Clipboard is currently not supported on the web.', - ); - return; - } - if (!QuillNativeBridge.isClipboardOperationsSupported) { - scaffoldMessenger.showText( - 'Getting HTML from the Clipboard is not supported on ${defaultTargetPlatform.name}', - ); - return; - } - final result = await QuillNativeBridge.getClipboardHTML(); - if (result == null) { - scaffoldMessenger.showText( - 'The HTML is not available on the clipboard.', - ); - return; - } - scaffoldMessenger.showText( - 'HTML copied to clipboard: $result', - ); - await Clipboard.setData(ClipboardData(text: result)); - debugPrint('HTML from the clipboard: $result'); - }, - child: const Text('Get HTML from Clipboard'), + ElevatedButton.icon( + onPressed: () => _onButtonClick( + QuillNativeBridgePlatformFeature.getClipboardHTML, + context: context, + ), + label: const Text('Get HTML from Clipboard'), + icon: const Icon(Icons.html), ), - ElevatedButton( - onPressed: () async { - final scaffoldMessenger = ScaffoldMessenger.of(context); - if (kIsWeb) { - scaffoldMessenger.showText( - 'Copying an image to the clipboard is currently not supported on web.', - ); - return; - } - if (!QuillNativeBridge.isClipboardOperationsSupported) { - scaffoldMessenger.showText( - 'Copying an image to the Clipboard is not supported on ${defaultTargetPlatform.name}', - ); - return; - } - final imageBytes = (await rootBundle.load(_kFlutterQuillAssetImage)) - .buffer - .asUint8List(); - await QuillNativeBridge.copyImageToClipboard(imageBytes); - - // Not widely supported but some apps copy the image as a text: - // final file = File( - // '${Directory.systemTemp.path}/clipboard-image.png', - // ); - // await file.create(recursive: true); - // await file.writeAsBytes(imageBytes); - // Clipboard.setData( - // ClipboardData( - // // Currently the Android plugin doesn't support content:// - // text: 'file://${file.absolute.path}', - // ), - // ); - - scaffoldMessenger.showText( - 'Image has been copied to the clipboard.', - ); - }, - child: const Text('Copy Image to Clipboard'), + ElevatedButton.icon( + onPressed: () => _onButtonClick( + QuillNativeBridgePlatformFeature.copyImageToClipboard, + context: context, + ), + label: const Text('Copy Image to Clipboard'), + icon: const Icon(Icons.copy), ), - ElevatedButton( - onPressed: () async { - final scaffoldMessenger = ScaffoldMessenger.of(context); - if (kIsWeb) { - scaffoldMessenger.showText( - 'Retriving an image from the clipboard is currently not supported on web.', - ); - return; - } - if (!QuillNativeBridge.isClipboardOperationsSupported) { - scaffoldMessenger.showText( - 'Retriving an image from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', - ); - return; - } - final imageBytes = await QuillNativeBridge.getClipboardImage(); - if (imageBytes == null) { - scaffoldMessenger.showText( - 'The image is not available on the clipboard.', - ); - return; - } - if (!context.mounted) { - return; - } - showDialog( - context: context, - builder: (context) => Dialog( - child: Image.memory(imageBytes), - ), - ); - }, - child: const Text('Retrive Image from Clipboard'), + ElevatedButton.icon( + onPressed: () => _onButtonClick( + QuillNativeBridgePlatformFeature.getClipboardImage, + context: context, + ), + label: const Text('Retrieve Image from Clipboard'), + icon: const Icon(Icons.image), + ), + ElevatedButton.icon( + onPressed: () => _onButtonClick( + QuillNativeBridgePlatformFeature.getClipboardGif, + context: context, + ), + label: const Text('Retrieve Gif from Clipboard'), + icon: const Icon(Icons.gif), ), ], ); } + + Future _onButtonClick( + QuillNativeBridgePlatformFeature platformFeature, { + required BuildContext context, + }) async { + final isFeatureUnsupported = platformFeature.isUnsupported; + final isFeatureWebUnsupported = !platformFeature.hasWebSupport && kIsWeb; + switch (platformFeature) { + case QuillNativeBridgePlatformFeature.isIOSSimulator: + final scaffoldMessenger = ScaffoldMessenger.of(context); + if (isFeatureUnsupported) { + scaffoldMessenger.showText( + isFeatureWebUnsupported + ? "Can't check if the device is iOS simulator on the web." + : 'Must be on iOS to check if simualtor.', + ); + return; + } + final result = await QuillNativeBridge.isIOSSimulator(); + scaffoldMessenger.showText(result + ? "You're running the app on iOS simulator" + : "You're running the app on real iOS device."); + break; + case QuillNativeBridgePlatformFeature.getClipboardHTML: + final scaffoldMessenger = ScaffoldMessenger.of(context); + if (isFeatureUnsupported) { + scaffoldMessenger.showText( + isFeatureWebUnsupported + ? 'Retriving HTML from the Clipboard is currently not supported on the web.' + : 'Getting HTML from the Clipboard is not supported on ${defaultTargetPlatform.name}', + ); + return; + } + final result = await QuillNativeBridge.getClipboardHTML(); + if (result == null) { + scaffoldMessenger.showText( + 'The HTML is not available on the clipboard.', + ); + return; + } + scaffoldMessenger.showText( + 'HTML copied to clipboard: $result', + ); + await Clipboard.setData(ClipboardData(text: result)); + debugPrint('HTML from the clipboard: $result'); + break; + case QuillNativeBridgePlatformFeature.copyImageToClipboard: + final scaffoldMessenger = ScaffoldMessenger.of(context); + if (isFeatureUnsupported) { + scaffoldMessenger.showText( + isFeatureWebUnsupported + ? 'Copying an image to the clipboard is currently not supported on web.' + : 'Copying an image to the Clipboard is not supported on ${defaultTargetPlatform.name}', + ); + return; + } + final imageBytes = (await rootBundle.load(_kFlutterQuillAssetImage)) + .buffer + .asUint8List(); + await QuillNativeBridge.copyImageToClipboard(imageBytes); + + // Not widely supported but some apps copy the image as a text: + // final file = File( + // '${Directory.systemTemp.path}/clipboard-image.png', + // ); + // await file.create(recursive: true); + // await file.writeAsBytes(imageBytes); + // Clipboard.setData( + // ClipboardData( + // // Currently the Android plugin doesn't support content:// + // text: 'file://${file.absolute.path}', + // ), + // ); + + scaffoldMessenger.showText( + 'Image has been copied to the clipboard.', + ); + break; + case QuillNativeBridgePlatformFeature.getClipboardImage: + final scaffoldMessenger = ScaffoldMessenger.of(context); + if (isFeatureUnsupported) { + scaffoldMessenger.showText( + isFeatureWebUnsupported + ? 'Retriving an image from the clipboard is currently not supported on web.' + : 'Retriving an image from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', + ); + return; + } + final imageBytes = await QuillNativeBridge.getClipboardImage(); + if (imageBytes == null) { + scaffoldMessenger.showText( + 'The image is not available on the clipboard.', + ); + return; + } + if (!context.mounted) { + return; + } + showDialog( + context: context, + builder: (context) => Dialog( + child: Image.memory(imageBytes), + ), + ); + break; + case QuillNativeBridgePlatformFeature.getClipboardGif: + final scaffoldMessenger = ScaffoldMessenger.of(context); + if (isFeatureUnsupported) { + scaffoldMessenger.showText( + isFeatureWebUnsupported + ? 'Retriving a gif from the clipboard is currently not supported on web.' + : 'Retriving a gif from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', + ); + return; + } + final gifBytes = await QuillNativeBridge.getClipboardGif(); + if (gifBytes == null) { + scaffoldMessenger.showText( + 'The gif is not available on the clipboard.', + ); + return; + } + if (!context.mounted) { + return; + } + showDialog( + context: context, + builder: (context) => Dialog( + child: Image.memory(gifBytes), + ), + ); + break; + } + } } extension ScaffoldMessengerX on ScaffoldMessengerState { diff --git a/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift index edac60fe6..53428a10a 100644 --- a/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift +++ b/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift @@ -39,6 +39,9 @@ public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { let image = UIPasteboard.general.image let data = image?.pngData() result(data) + case "getClipboardGif": + let data = UIPasteboard.general.data(forPasteboardType: "com.compuserve.gif") + result(data) default: result(FlutterMethodNotImplemented) } diff --git a/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/lib/quill_native_bridge.dart index 9450ab941..52a6bcd68 100644 --- a/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/lib/quill_native_bridge.dart @@ -3,10 +3,15 @@ library; import 'package:flutter/foundation.dart' show TargetPlatform, Uint8List, defaultTargetPlatform, kIsWeb; +import 'quill_native_bridge.dart'; import 'src/quill_native_bridge_platform_interface.dart'; +export 'src/platform_feature.dart'; + /// An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) /// package to access platform-specific APIs. +/// +/// See [QuillNativeBridgePlatformFeature] to check whatever if a feature is supported. class QuillNativeBridge { QuillNativeBridge._(); @@ -17,18 +22,6 @@ class QuillNativeBridge { static Future isIOSSimulator() => QuillNativeBridgePlatform.instance.isIOSSimulator(); - /// **Experimental** and might removed in future releases. - /// - /// For now we do plan on removing this property once all non-web platforms - /// are supported. - /// - /// Available to avoid hardcoding. - static const Set _supportedClipboardPlatforms = { - TargetPlatform.android, - TargetPlatform.iOS, - TargetPlatform.macOS - }; - /// Return HTML from the Clipboard for **non-web platforms**. /// /// Doesn't support web, should use @@ -44,12 +37,6 @@ class QuillNativeBridge { static Future getClipboardHTML() => QuillNativeBridgePlatform.instance.getClipboardHTML(); - /// **Experimental** and might removed in future releases. - /// - /// Available to avoid hardcoding. - static bool get isClipboardOperationsSupported => - _supportedClipboardPlatforms.contains(defaultTargetPlatform); - /// Copy the [imageBytes] to Clipboard to be pasted on other apps. /// /// Require modifying `AndroidManifest.xml` to work on **Android**. @@ -66,4 +53,10 @@ class QuillNativeBridge { /// Currently only supports **Android**, **iOS**, **macOS**. static Future getClipboardImage() => QuillNativeBridgePlatform.instance.getClipboardImage(); + + /// Return the copied gif from the Clipboard. + /// + /// Currently only supports **Android**, **iOS**. + static Future getClipboardGif() => + QuillNativeBridgePlatform.instance.getClipboardGif(); } diff --git a/quill_native_bridge/lib/src/platform_feature.dart b/quill_native_bridge/lib/src/platform_feature.dart new file mode 100644 index 000000000..30f0d2a40 --- /dev/null +++ b/quill_native_bridge/lib/src/platform_feature.dart @@ -0,0 +1,64 @@ +import 'package:flutter/foundation.dart' + show TargetPlatform, defaultTargetPlatform, kIsWeb; + +/// The features/methods provided by the plugin +enum QuillNativeBridgePlatformFeature { + isIOSSimulator(hasWebSupport: false), + getClipboardHTML(hasWebSupport: false), + copyImageToClipboard(hasWebSupport: false), + getClipboardImage(hasWebSupport: false), + getClipboardGif(hasWebSupport: false); + + const QuillNativeBridgePlatformFeature({required this.hasWebSupport}); + + /// Verify if this feature is supported on web regardless of the [TargetPlatform]. + /// + /// **Note**: This doesn't check whatever if the web browser support this + /// specific feature. + /// + /// For example the **Clipboard API** is not supported on **Firefox** + /// but is supported on the web itself in general, the [hasWebSupport] + /// will return `true`. + /// + /// Always check the docs of the method you're calling to see if there + /// are special notes. For this specific example, you will need + /// to fallback to **Clipboard events** on **Firefox** or browsers that doesn't + /// support **Clipboard API**. + final bool hasWebSupport; + + // Note: the [hasWebSupport] need to be manually updated to be in sync with + // [isSupported] + + /// Verify whether a specific feature is supported by the plugin for the [TargetPlatform]. + /// + /// **Note**: This doesn't check if the platform operating system does support + /// this feature. It only check if this feature is supported + /// on a specific platform (e.g. **Android** or **iOS**). + /// + /// If feature A is not supported on **Android API 21** (for example), + /// then the [isSupported] doesn't cover this case. + /// + /// Always check the docs of the method you're calling to see if there + /// are special notes. + bool get isSupported { + return switch (this) { + QuillNativeBridgePlatformFeature.isIOSSimulator => + !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS, + QuillNativeBridgePlatformFeature.getClipboardHTML => !kIsWeb && + {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} + .contains(defaultTargetPlatform), + QuillNativeBridgePlatformFeature.copyImageToClipboard => !kIsWeb && + {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} + .contains(defaultTargetPlatform), + QuillNativeBridgePlatformFeature.getClipboardImage => !kIsWeb && + {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} + .contains(defaultTargetPlatform), + QuillNativeBridgePlatformFeature.getClipboardGif => !kIsWeb && + {TargetPlatform.android, TargetPlatform.iOS} + .contains(defaultTargetPlatform), + }; + } + + /// Negation of [isSupported] + bool get isUnsupported => !isSupported; +} diff --git a/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart index 69161cdcd..553bf703d 100644 --- a/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart @@ -11,7 +11,7 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { @override Future isIOSSimulator() async { assert(() { - if (kIsWeb || defaultTargetPlatform != TargetPlatform.iOS) { + if (QuillNativeBridgePlatformFeature.isIOSSimulator.isUnsupported) { throw FlutterError( 'isIOSSimulator() method should be called only on iOS.', ); @@ -34,14 +34,9 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { @override Future getClipboardHTML() async { assert(() { - if (kIsWeb) { + if (QuillNativeBridgePlatformFeature.getClipboardHTML.isUnsupported) { throw FlutterError( - 'getClipboardHTML() method should be only called on non-web platforms.', - ); - } - if (!QuillNativeBridge.isClipboardOperationsSupported) { - throw FlutterError( - 'getClipboardHTML() currently only supports Android, iOS and macOS.', + 'getClipboardHTML() is currently not supported on $defaultTargetPlatform.', ); } return true; @@ -54,9 +49,9 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { @override Future copyImageToClipboard(Uint8List imageBytes) async { assert(() { - if (!QuillNativeBridge.isClipboardOperationsSupported) { + if (QuillNativeBridgePlatformFeature.copyImageToClipboard.isUnsupported) { throw FlutterError( - 'copyImageToClipboard() currently only supports Android, iOS, macOS and Web.', + 'copyImageToClipboard() is currently not supported on $defaultTargetPlatform.', ); } return true; @@ -74,7 +69,7 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { 'to support copying images to the clipboard on Android.\n' "If you're interested in this feature, refer to https://github.com/singerdmx/flutter-quill#-platform-specific-configurations\n" 'This message will only shown in debug mode.\n' - 'More details: ${e.message}', + 'Platform details: ${e.toString()}', ); return; } @@ -82,11 +77,84 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { } } + // TODO: getClipboardImage() should not return gif files on macOS and iOS, same as Android impl + @override Future getClipboardImage() async { - final imageBytes = await methodChannel.invokeMethod( - 'getClipboardImage', + assert(() { + if (QuillNativeBridgePlatformFeature.getClipboardImage.isUnsupported) { + throw FlutterError( + 'getClipboardImage() is currently not supported on $defaultTargetPlatform.', + ); + } + return true; + }()); + try { + final imageBytes = await methodChannel.invokeMethod( + 'getClipboardImage', + ); + return imageBytes; + } on PlatformException catch (e) { + if ((kDebugMode && defaultTargetPlatform == TargetPlatform.android) && + (e.code == 'FILE_READ_PERMISSION_DENIED' || + e.code == 'FILE_NOT_FOUND')) { + _printAndroidClipboardImageAccessKnownIssue(e); + return null; + } + rethrow; + } + } + + @override + Future getClipboardGif() async { + assert(() { + if (QuillNativeBridgePlatformFeature.getClipboardGif.isUnsupported) { + throw FlutterError( + 'getClipboardGif() is currently not supported on $defaultTargetPlatform.', + ); + } + return true; + }()); + try { + final gifBytes = await methodChannel.invokeMethod( + 'getClipboardGif', + ); + return gifBytes; + } on PlatformException catch (e) { + if ((kDebugMode && defaultTargetPlatform == TargetPlatform.android) && + (e.code == 'FILE_READ_PERMISSION_DENIED' || + e.code == 'FILE_NOT_FOUND')) { + _printAndroidClipboardImageAccessKnownIssue(e); + return null; + } + rethrow; + } + } + + /// Should be only used internally for [getClipboardGif] and [getClipboardImage] + /// for **Android only**. + /// + /// This issue can be caused by `SecurityException` or `FileNotFoundException` + /// from Android side. + /// + /// See [#2243](https://github.com/singerdmx/flutter-quill/issues/2243) for more details. + void _printAndroidClipboardImageAccessKnownIssue(PlatformException e) { + assert( + defaultTargetPlatform == TargetPlatform.android, + '_printAndroidClipboardImageAccessKnownIssue() should be only used for Android.', + ); + assert( + kDebugMode, + '_printAndroidClipboardImageAccessKnownIssue() should be only called in debug mode', ); - return imageBytes; + if (kDebugMode) { + debugPrint( + 'Could not retrieve the image from clipbaord as the app no longer have access to the image.\n' + 'This can happen on app restart or lifecycle changes.\n' + 'This is known issue on Android and this message will be only shown in debug mode.\n' + 'Refer to https://github.com/singerdmx/flutter-quill/issues/2243 for discussion.\n' + 'Platform details: ${e.toString()}', + ); + } } } diff --git a/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart b/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart index e2437b8a6..0e46e66fb 100644 --- a/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart @@ -42,4 +42,8 @@ abstract class QuillNativeBridgePlatform extends PlatformInterface { /// Return the copied image from the Clipboard. Future getClipboardImage() => throw UnimplementedError('getClipboardImage() has not been implemented.'); + + /// Return the copied gif from the Clipboard. + Future getClipboardGif() => + throw UnimplementedError('getClipboardGif() has not been implemented.'); } diff --git a/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift index 228e9e33f..5e6146b93 100644 --- a/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift +++ b/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift @@ -55,6 +55,10 @@ public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { } else { result(nil) } + case "getClipboardGif": + // TODO: Add support for getClipboardGif() on macOS if possible + let availableTypes = NSPasteboard.general.types + result(FlutterError(code: "GIF_UNSUPPORTED", message: "Gif image is not supported on macOS. Available types: \(String(describing: availableTypes))", details: nil)) default: result(FlutterMethodNotImplemented) } diff --git a/quill_native_bridge/test/quill_native_bridge_test.dart b/quill_native_bridge/test/quill_native_bridge_test.dart index 6d4e0ff76..707f61f3c 100644 --- a/quill_native_bridge/test/quill_native_bridge_test.dart +++ b/quill_native_bridge/test/quill_native_bridge_test.dart @@ -27,6 +27,11 @@ class MockQuillNativeBridgePlatform Future getClipboardImage() async { return Uint8List.fromList([0, 2, 1]); } + + @override + Future getClipboardGif() async { + return Uint8List.fromList([0, 1, 0]); + } } void main() { @@ -64,10 +69,17 @@ void main() { ); }); - test('copyImageToClipboard()', () async { + test('getClipboardImage()', () async { expect( await QuillNativeBridgePlatform.instance.getClipboardImage(), Uint8List.fromList([0, 2, 1]), ); }); + + test('getClipboardGif()', () async { + expect( + await QuillNativeBridgePlatform.instance.getClipboardGif(), + Uint8List.fromList([0, 1, 0]), + ); + }); } From ffba185cce03a3c7a9ffb376a1289bd8c9a54319 Mon Sep 17 00:00:00 2001 From: Ellet Date: Thu, 19 Sep 2024 17:32:50 +0300 Subject: [PATCH 17/90] chore(example): configure the Android example for copying images to the system clipboard, update README.md --- README.md | 3 ++- example/android/app/src/main/AndroidManifest.xml | 11 +++++++++++ example/android/app/src/main/res/xml/file_paths.xml | 3 +++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 example/android/app/src/main/res/xml/file_paths.xml diff --git a/README.md b/README.md index a716c7fc6..d7395b331 100644 --- a/README.md +++ b/README.md @@ -114,11 +114,12 @@ The `flutter_quill` package uses the following plugins: ### Android Configuration for `quill_native_bridge` -To support copying images to the clipboard, you need to configure your Android project. +To support copying images to the clipboard to be accessed by other apps, you need to configure your Android project. If not set up, a warning will appear in the log during debug mode only. > [!TIP] > This is only required on **Android** for this optional feature. +> You should be able to copy images and paste them inside the editor without any additional configuration. **1. Update `AndroidManifest.xml`** diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index e68e61dfa..8e27c9fc5 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -62,5 +62,16 @@ android:authorities="com.example.example.SuperClipboardDataProvider" android:exported="true" android:grantUriPermissions="true" /> + + + + + \ No newline at end of file diff --git a/example/android/app/src/main/res/xml/file_paths.xml b/example/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 000000000..11a4d5070 --- /dev/null +++ b/example/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file From da772438461a7a7a62707d3f0c906042d59fb990 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 21 Sep 2024 01:09:49 +0300 Subject: [PATCH 18/90] feat: support for web (WIP, will be updated soon) --- .../lib/src/platform_feature.dart | 12 +-- .../src/web/clipboard_api_support_unsafe.dart | 20 +++++ .../lib/src/web/quill_native_bridge_web.dart | 82 +++++++++++++++---- 3 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 quill_native_bridge/lib/src/web/clipboard_api_support_unsafe.dart diff --git a/quill_native_bridge/lib/src/platform_feature.dart b/quill_native_bridge/lib/src/platform_feature.dart index 30f0d2a40..22df887e6 100644 --- a/quill_native_bridge/lib/src/platform_feature.dart +++ b/quill_native_bridge/lib/src/platform_feature.dart @@ -4,9 +4,9 @@ import 'package:flutter/foundation.dart' /// The features/methods provided by the plugin enum QuillNativeBridgePlatformFeature { isIOSSimulator(hasWebSupport: false), - getClipboardHTML(hasWebSupport: false), - copyImageToClipboard(hasWebSupport: false), - getClipboardImage(hasWebSupport: false), + getClipboardHTML(hasWebSupport: true), + copyImageToClipboard(hasWebSupport: true), + getClipboardImage(hasWebSupport: true), getClipboardGif(hasWebSupport: false); const QuillNativeBridgePlatformFeature({required this.hasWebSupport}); @@ -44,13 +44,13 @@ enum QuillNativeBridgePlatformFeature { return switch (this) { QuillNativeBridgePlatformFeature.isIOSSimulator => !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS, - QuillNativeBridgePlatformFeature.getClipboardHTML => !kIsWeb && + QuillNativeBridgePlatformFeature.getClipboardHTML => kIsWeb || {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} .contains(defaultTargetPlatform), - QuillNativeBridgePlatformFeature.copyImageToClipboard => !kIsWeb && + QuillNativeBridgePlatformFeature.copyImageToClipboard => kIsWeb || {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} .contains(defaultTargetPlatform), - QuillNativeBridgePlatformFeature.getClipboardImage => !kIsWeb && + QuillNativeBridgePlatformFeature.getClipboardImage => kIsWeb || {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} .contains(defaultTargetPlatform), QuillNativeBridgePlatformFeature.getClipboardGif => !kIsWeb && diff --git a/quill_native_bridge/lib/src/web/clipboard_api_support_unsafe.dart b/quill_native_bridge/lib/src/web/clipboard_api_support_unsafe.dart new file mode 100644 index 000000000..bc3971130 --- /dev/null +++ b/quill_native_bridge/lib/src/web/clipboard_api_support_unsafe.dart @@ -0,0 +1,20 @@ +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; + +import 'package:web/web.dart'; + +// Should minimize the usage of [dart:js_interop_unsafe] when possible. +// Importing [dart:js_interop_unsafe] into it's own file +// to avoid accidentally using APIs from it. + +/// Verify if the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) +/// is supported and available. +/// +/// Can be `false` for some browsers (e.g. **Firefox**), fallback to +/// Clipboard events (e.g. [paste_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event)). +bool get isClipboardApiSupported => + window.navigator.getProperty('clipboard'.toJS) != null && + window.navigator.hasProperty('clipboard'.toJS).toDart; + +/// Negation of [isClipboardApiSupported] +bool get isClipbaordApiUnsupported => !isClipboardApiSupported; diff --git a/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart b/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart index e1bdde07d..b3898ea4e 100644 --- a/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart +++ b/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart @@ -8,15 +8,16 @@ import 'dart:js_interop'; -import 'package:flutter/foundation.dart' show Uint8List; +import 'package:flutter/foundation.dart' show Uint8List, debugPrint; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:web/web.dart'; import '../quill_native_bridge_platform_interface.dart'; +import 'clipboard_api_support_unsafe.dart'; /// A web implementation of the [QuillNativeBridgePlatform]. /// -/// **Experimental** and can be removed. +/// **Highly Experimental** and can be removed. /// /// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: /// @@ -34,6 +35,13 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { @override Future copyImageToClipboard(Uint8List imageBytes) async { + if (isClipbaordApiUnsupported) { + throw UnsupportedError( + 'Could not copy image to the clipboard.\n' + 'The Clipboard API is not supported on ${window.navigator.userAgent}.\n' + 'Should fallback to Clipboard events.', + ); + } final blob = Blob( [imageBytes.toJS].jsify() as JSArray, BlobPropertyBag(type: 'image/png'), @@ -43,27 +51,65 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { {'image/png': blob}.jsify() as JSObject, ); - // TODO: Will cause issue on Firefox as Clipboard API is not supported, fallback to Clipboard events. await window.navigator.clipboard .write([clipboardItem].jsify() as ClipboardItems) .toDart; } - // TODO: This web implementation doesn't work on firefox. - // Related: https://github.com/singerdmx/flutter-quill/issues/2220 + @override + Future getClipboardHTML() async { + if (isClipbaordApiUnsupported) { + throw UnsupportedError( + 'Could not retrieve HTML from the clipboard.\n' + 'The Clipboard API is not supported on ${window.navigator.userAgent}.\n' + 'Should fallback to Clipboard events.', + ); + } + const kMimeTextHtml = 'text/html'; + final clipboardItems = + (await window.navigator.clipboard.read().toDart).toDart; + for (final item in clipboardItems) { + if (item.types.toDart.contains(kMimeTextHtml.toJS)) { + final html = await item.getType(kMimeTextHtml).toDart; + return (await html.text().toDart).toDart; + } + } + return null; + } - // TODO: Need a method to check if Clipboard API is supported (which is not on Firefox) + @override + Future getClipboardImage() async { + if (isClipbaordApiUnsupported) { + throw UnsupportedError( + 'Could not retrieve image from the clipboard.\n' + 'The Clipboard API is not supported on ${window.navigator.userAgent}.\n' + 'Should fallback to Clipboard events.', + ); + } + const kMimeImagePng = 'image/png'; + final clipboardItems = + (await window.navigator.clipboard.read().toDart).toDart; + for (final item in clipboardItems) { + if (item.types.toDart.contains(kMimeImagePng.toJS)) { + final blob = await item.getType(kMimeImagePng).toDart; + final arrayBuffer = await blob.arrayBuffer().toDart; + return arrayBuffer.toDart.asUint8List(); + } + } + return null; + } - // @override - // Future getClipboardHTML() async { - // final clipboardData = - // (await window.navigator.clipboard.read().toDart).toDart; - // for (final item in clipboardData) { - // if (item.types.toDart.contains('text/html'.toJS)) { - // final html = await item.getType('text/html').toDart; - // return (await html.text().toDart).toDart; - // } - // } - // return null; - // } + @override + Future getClipboardGif() { + assert(() { + debugPrint( + 'Retrieving gif image from the clipboard is unsupported regardless of the browser.\n' + 'Refer to https://github.com/singerdmx/flutter-quill/issues/2229 for discussion.', + ); + return true; + }()); + throw UnsupportedError( + 'Retrieving gif image from the clipboard is unsupported regardless of the browser.', + ); + } } From 499bdeeb6238137b299530ed06cadd8cbc52ccac Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 21 Sep 2024 16:50:24 +0300 Subject: [PATCH 19/90] chore: annotate ClipboardService and related classes as experimental --- .../clipboard/super_clipboard_service.dart | 2 ++ .../clipboard/clipboard_service.dart | 5 ++++- .../clipboard/clipboard_service_provider.dart | 3 +++ .../clipboard/default_clipboard_service.dart | 5 ++++- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/flutter_quill_extensions/lib/src/editor_toolbar_controller_shared/clipboard/super_clipboard_service.dart b/flutter_quill_extensions/lib/src/editor_toolbar_controller_shared/clipboard/super_clipboard_service.dart index ca7f6aa6b..e144f01c1 100644 --- a/flutter_quill_extensions/lib/src/editor_toolbar_controller_shared/clipboard/super_clipboard_service.dart +++ b/flutter_quill_extensions/lib/src/editor_toolbar_controller_shared/clipboard/super_clipboard_service.dart @@ -4,10 +4,12 @@ import 'dart:convert' show utf8; import 'package:flutter/foundation.dart'; import 'package:flutter_quill/flutter_quill_internal.dart' show ClipboardService; +import 'package:meta/meta.dart' show experimental; import 'package:super_clipboard/super_clipboard.dart'; /// Implementation using the https://pub.dev/packages/super_clipboard plugin. +@experimental class SuperClipboardService extends ClipboardService { /// [Null] if the Clipboard API is not supported on this platform /// https://pub.dev/packages/super_clipboard#usage diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart index 7bdad7f54..67a20714d 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart @@ -1,7 +1,10 @@ import 'package:flutter/foundation.dart' show Uint8List; import 'package:flutter/services.dart' show Clipboard; +import 'package:meta/meta.dart' show experimental; -/// An abstraction to make it easy to provide different implementations +/// A more rich abstraction of Flutter [Clipboard] to support images, rich text +/// and more clipboard operations. +@experimental abstract class ClipboardService { /// Return HTML from the Clipboard. Future getHtmlText(); diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service_provider.dart b/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service_provider.dart index e8208cf95..aa7b9c402 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service_provider.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service_provider.dart @@ -1,6 +1,9 @@ +import 'package:meta/meta.dart' show experimental; + import 'clipboard_service.dart'; import 'default_clipboard_service.dart'; +@experimental class ClipboardServiceProvider { ClipboardServiceProvider._(); static ClipboardService _instance = DefaultClipboardService(); diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart index 03e8ede20..cdef6a617 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart @@ -1,10 +1,13 @@ import 'package:flutter/services.dart' show Uint8List; +import 'package:meta/meta.dart' show experimental; import 'package:quill_native_bridge/quill_native_bridge.dart' show QuillNativeBridge, QuillNativeBridgePlatformFeature; import 'clipboard_service.dart'; -/// Default implementation +/// Default implementation of [ClipboardService] to support rich clipboard +/// operations. +@experimental class DefaultClipboardService extends ClipboardService { @override Future getHtmlText() async { From d89073a3c8b7a63e731aec590d005532e69fab78 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 21 Sep 2024 18:54:23 +0300 Subject: [PATCH 20/90] chore: temporarily add dependency_overrides to fix build failure - revert this change later --- flutter_quill_extensions/pubspec_overrides.yaml | 4 ++++ pubspec_overrides.yaml | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 flutter_quill_extensions/pubspec_overrides.yaml create mode 100644 pubspec_overrides.yaml diff --git a/flutter_quill_extensions/pubspec_overrides.yaml b/flutter_quill_extensions/pubspec_overrides.yaml new file mode 100644 index 000000000..b4f64788e --- /dev/null +++ b/flutter_quill_extensions/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing +dependency_overrides: + flutter_quill: + path: ../ \ No newline at end of file diff --git a/pubspec_overrides.yaml b/pubspec_overrides.yaml new file mode 100644 index 000000000..cbcbb8dbe --- /dev/null +++ b/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing +dependency_overrides: + quill_native_bridge: + path: ./quill_native_bridge \ No newline at end of file From d8c8f1046479787e43827856fd7e3b7b8d94da96 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 21 Sep 2024 21:00:52 +0300 Subject: [PATCH 21/90] feat: seperate web implementation from quill_native_bridge into quill_native_bridge_web --- example/pubspec.yaml | 2 +- pubspec_overrides.yaml | 2 +- quill_native_bridge/CHANGELOG.md | 2970 ----------------- .../{ => quill_native_bridge}/.gitignore | 0 .../{ => quill_native_bridge}/.metadata | 0 .../quill_native_bridge/CHANGELOG.md | 7 + .../{ => quill_native_bridge}/LICENSE | 0 .../{ => quill_native_bridge}/README.md | 0 .../analysis_options.yaml | 0 .../android/.gitignore | 0 .../android/build.gradle | 0 .../android/settings.gradle | 0 .../android/src/main/AndroidManifest.xml | 0 .../QuillNativeBridgePlugin.kt | 0 .../clipboard/ClipboardImageHandler.kt | 0 .../example/.gitignore | 0 .../example/README.md | 0 .../example/analysis_options.yaml | 0 .../example/android/.gitignore | 0 .../example/android/app/build.gradle | 0 .../android/app/src/debug/AndroidManifest.xml | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../MainActivity.kt | 0 .../res/drawable-v21/launch_background.xml | 0 .../main/res/drawable/launch_background.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/values-night/styles.xml | 0 .../app/src/main/res/values/styles.xml | 0 .../app/src/main/res/xml/file_paths.xml | 0 .../app/src/profile/AndroidManifest.xml | 0 .../example/android/build.gradle | 0 .../example/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../example/android/settings.gradle | 0 .../example/assets/flutter-quill.png | Bin .../example/ios/.gitignore | 0 .../ios/Flutter/AppFrameworkInfo.plist | 0 .../example/ios/Flutter/Debug.xcconfig | 0 .../example/ios/Flutter/Release.xcconfig | 0 .../example/ios/Podfile | 0 .../example/ios/Podfile.lock | 0 .../ios/Runner.xcodeproj/project.pbxproj | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../example/ios/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../LaunchImage.imageset/README.md | 0 .../Runner/Base.lproj/LaunchScreen.storyboard | 0 .../ios/Runner/Base.lproj/Main.storyboard | 0 .../example/ios/Runner/Info.plist | 0 .../ios/Runner/Runner-Bridging-Header.h | 0 .../example/lib/main.dart | 0 .../example/macos/.gitignore | 0 .../macos/Flutter/Flutter-Debug.xcconfig | 0 .../macos/Flutter/Flutter-Release.xcconfig | 0 .../Flutter/GeneratedPluginRegistrant.swift | 0 .../example/macos/Podfile | 0 .../example/macos/Podfile.lock | 0 .../macos/Runner.xcodeproj/project.pbxproj | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../example/macos/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/app_icon_1024.png | Bin .../AppIcon.appiconset/app_icon_128.png | Bin .../AppIcon.appiconset/app_icon_16.png | Bin .../AppIcon.appiconset/app_icon_256.png | Bin .../AppIcon.appiconset/app_icon_32.png | Bin .../AppIcon.appiconset/app_icon_512.png | Bin .../AppIcon.appiconset/app_icon_64.png | Bin .../macos/Runner/Base.lproj/MainMenu.xib | 0 .../macos/Runner/Configs/AppInfo.xcconfig | 0 .../macos/Runner/Configs/Debug.xcconfig | 0 .../macos/Runner/Configs/Release.xcconfig | 0 .../macos/Runner/Configs/Warnings.xcconfig | 0 .../macos/Runner/DebugProfile.entitlements | 0 .../example/macos/Runner/Info.plist | 0 .../macos/Runner/MainFlutterWindow.swift | 0 .../example/macos/Runner/Release.entitlements | 0 .../example/pubspec.yaml | 0 .../example/web/favicon.png | Bin .../example/web/icons/Icon-192.png | Bin .../example/web/icons/Icon-512.png | Bin .../example/web/icons/Icon-maskable-192.png | Bin .../example/web/icons/Icon-maskable-512.png | Bin .../example/web/index.html | 0 .../example/web/manifest.json | 0 .../{ => quill_native_bridge}/ios/.gitignore | 0 .../ios/Assets/.gitkeep | 0 .../ios/Classes/QuillNativeBridgePlugin.swift | 0 .../ios/Resources/PrivacyInfo.xcprivacy | 0 .../ios/quill_native_bridge.podspec | 0 .../lib/quill_native_bridge.dart | 3 +- .../lib/src/platform_feature.dart | 0 .../quill_native_bridge_method_channel.dart | 2 +- ...uill_native_bridge_platform_interface.dart | 1 + .../Classes/QuillNativeBridgePlugin.swift | 0 .../macos/quill_native_bridge.podspec | 0 .../{ => quill_native_bridge}/pubspec.yaml | 19 +- ...ill_native_bridge_method_channel_test.dart | 0 .../test/quill_native_bridge_test.dart | 1 - .../quill_native_bridge_web/CHANGELOG.md | 8 + .../quill_native_bridge_web/README.md | 13 + .../lib}/quill_native_bridge_web.dart | 9 +- .../src}/clipboard_api_support_unsafe.dart | 0 .../quill_native_bridge_web/pubspec.yaml | 33 + 136 files changed, 77 insertions(+), 2993 deletions(-) delete mode 100644 quill_native_bridge/CHANGELOG.md rename quill_native_bridge/{ => quill_native_bridge}/.gitignore (100%) rename quill_native_bridge/{ => quill_native_bridge}/.metadata (100%) create mode 100644 quill_native_bridge/quill_native_bridge/CHANGELOG.md rename quill_native_bridge/{ => quill_native_bridge}/LICENSE (100%) rename quill_native_bridge/{ => quill_native_bridge}/README.md (100%) rename quill_native_bridge/{ => quill_native_bridge}/analysis_options.yaml (100%) rename quill_native_bridge/{ => quill_native_bridge}/android/.gitignore (100%) rename quill_native_bridge/{ => quill_native_bridge}/android/build.gradle (100%) rename quill_native_bridge/{ => quill_native_bridge}/android/settings.gradle (100%) rename quill_native_bridge/{ => quill_native_bridge}/android/src/main/AndroidManifest.xml (100%) rename quill_native_bridge/{ => quill_native_bridge}/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt (100%) rename quill_native_bridge/{ => quill_native_bridge}/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardImageHandler.kt (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/.gitignore (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/README.md (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/analysis_options.yaml (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/.gitignore (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/build.gradle (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/debug/AndroidManifest.xml (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/main/AndroidManifest.xml (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/main/res/drawable-v21/launch_background.xml (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/main/res/drawable/launch_background.xml (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/main/res/values-night/styles.xml (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/main/res/values/styles.xml (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/main/res/xml/file_paths.xml (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/app/src/profile/AndroidManifest.xml (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/build.gradle (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/gradle.properties (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/gradle/wrapper/gradle-wrapper.properties (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/android/settings.gradle (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/assets/flutter-quill.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/.gitignore (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Flutter/AppFrameworkInfo.plist (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Flutter/Debug.xcconfig (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Flutter/Release.xcconfig (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Podfile (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Podfile.lock (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner.xcodeproj/project.pbxproj (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/AppDelegate.swift (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Base.lproj/Main.storyboard (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Info.plist (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/ios/Runner/Runner-Bridging-Header.h (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/lib/main.dart (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/.gitignore (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Flutter/Flutter-Debug.xcconfig (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Flutter/Flutter-Release.xcconfig (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Flutter/GeneratedPluginRegistrant.swift (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Podfile (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Podfile.lock (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner.xcodeproj/project.pbxproj (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner.xcworkspace/contents.xcworkspacedata (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/AppDelegate.swift (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Base.lproj/MainMenu.xib (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Configs/AppInfo.xcconfig (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Configs/Debug.xcconfig (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Configs/Release.xcconfig (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Configs/Warnings.xcconfig (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/DebugProfile.entitlements (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Info.plist (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/MainFlutterWindow.swift (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/macos/Runner/Release.entitlements (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/pubspec.yaml (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/web/favicon.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/web/icons/Icon-192.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/web/icons/Icon-512.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/web/icons/Icon-maskable-192.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/web/icons/Icon-maskable-512.png (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/web/index.html (100%) rename quill_native_bridge/{ => quill_native_bridge}/example/web/manifest.json (100%) rename quill_native_bridge/{ => quill_native_bridge}/ios/.gitignore (100%) rename quill_native_bridge/{ => quill_native_bridge}/ios/Assets/.gitkeep (100%) rename quill_native_bridge/{ => quill_native_bridge}/ios/Classes/QuillNativeBridgePlugin.swift (100%) rename quill_native_bridge/{ => quill_native_bridge}/ios/Resources/PrivacyInfo.xcprivacy (100%) rename quill_native_bridge/{ => quill_native_bridge}/ios/quill_native_bridge.podspec (100%) rename quill_native_bridge/{ => quill_native_bridge}/lib/quill_native_bridge.dart (96%) rename quill_native_bridge/{ => quill_native_bridge}/lib/src/platform_feature.dart (100%) rename quill_native_bridge/{ => quill_native_bridge}/lib/src/quill_native_bridge_method_channel.dart (99%) rename quill_native_bridge/{ => quill_native_bridge}/lib/src/quill_native_bridge_platform_interface.dart (97%) rename quill_native_bridge/{ => quill_native_bridge}/macos/Classes/QuillNativeBridgePlugin.swift (100%) rename quill_native_bridge/{ => quill_native_bridge}/macos/quill_native_bridge.podspec (100%) rename quill_native_bridge/{ => quill_native_bridge}/pubspec.yaml (77%) rename quill_native_bridge/{ => quill_native_bridge}/test/quill_native_bridge_method_channel_test.dart (100%) rename quill_native_bridge/{ => quill_native_bridge}/test/quill_native_bridge_test.dart (96%) create mode 100644 quill_native_bridge/quill_native_bridge_web/CHANGELOG.md create mode 100644 quill_native_bridge/quill_native_bridge_web/README.md rename quill_native_bridge/{lib/src/web => quill_native_bridge_web/lib}/quill_native_bridge_web.dart (91%) rename quill_native_bridge/{lib/src/web => quill_native_bridge_web/lib/src}/clipboard_api_support_unsafe.dart (100%) create mode 100644 quill_native_bridge/quill_native_bridge_web/pubspec.yaml diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 130eb0d43..3dd222969 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -62,7 +62,7 @@ dependency_overrides: flutter_quill_test: path: ../flutter_quill_test quill_native_bridge: - path: ../quill_native_bridge + path: ../quill_native_bridge/quill_native_bridge dev_dependencies: diff --git a/pubspec_overrides.yaml b/pubspec_overrides.yaml index cbcbb8dbe..0614c0c1c 100644 --- a/pubspec_overrides.yaml +++ b/pubspec_overrides.yaml @@ -1,4 +1,4 @@ # TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing dependency_overrides: quill_native_bridge: - path: ./quill_native_bridge \ No newline at end of file + path: ./quill_native_bridge/quill_native_bridge \ No newline at end of file diff --git a/quill_native_bridge/CHANGELOG.md b/quill_native_bridge/CHANGELOG.md deleted file mode 100644 index f29609c4b..000000000 --- a/quill_native_bridge/CHANGELOG.md +++ /dev/null @@ -1,2970 +0,0 @@ - - -# Changelog - -All notable changes to this project will be documented in this file. - -## 10.7.3 - -- Deprecate `FlutterQuillExtensions` in `flutter_quill_extensions` -- Update the minimum version of `flutter_quill` and `super_clipboard` in `flutter_quill_extensions` to avoid using deprecated code. - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.7.2...v10.7.3 - -## 10.7.2 - -## What's Changed -* chore: deprecate flutter_quill/extensions.dart by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2258 - -This is a minor release introduced to upload a new version of `flutter_quill` and `flutter_quill_extensions` to update the minimum required to avoid using deprecated code in `flutter_quill_extensions`. - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.7.1...v10.7.2 - -## 10.7.1 - -* chore: deprecate markdown_quill export, ignore warnings by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2256 -* chore: deprecate spell checker service by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2255 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.7.0...v10.7.1 - -## 10.7.0 - -* Chore: deprecate embed table feature by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2254 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.6.6...v10.7.0 - -## 10.6.6 - -* Bug fix: Removing check not allowing spell check on web by @joeserhtf in https://github.com/singerdmx/flutter-quill/pull/2252 - -## New Contributors -* @joeserhtf made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2252 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.6.5...v10.6.6 - -## 10.6.5 - -* Refine IME composing range styling by applying underline as text style by @agata in https://github.com/singerdmx/flutter-quill/pull/2244 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.6.4...v10.6.5 - -## 10.6.4 - -* fix: the composing text did not show an underline during IME conversion by @agata in https://github.com/singerdmx/flutter-quill/pull/2242 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.6.3...v10.6.4 - -## 10.6.3 - -* Fix: Resolved issue with broken IME composing rect in Windows desktop by @agata in https://github.com/singerdmx/flutter-quill/pull/2239 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.6.2...v10.6.3 - -## 10.6.2 - -* Fix: QuillToolbarToggleStyleButton Switching failure by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2234 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.6.1...v10.6.2 - -## 10.6.1 - -* Chore: update `flutter_quill_delta_from_html` to remove exception calls by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2232 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.6.0...v10.6.1 - -## 10.6.0 - -* docs: cleanup the docs, remove outdated resources, general changes by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2227 -* Feat: customizable character and space shortcut events by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2228 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.19...v10.6.0 - -## 10.5.19 - -* fix: properties other than 'style' for custom inline code styles (such as 'backgroundColor') were not being applied correctly by @agata in https://github.com/singerdmx/flutter-quill/pull/2226 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.18...v10.5.19 - -## 10.5.18 - -* feat(web): rich text paste from Clipboard using HTML by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2009 -* revert: disable rich text paste feature on web as a workaround by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2221 -* refactor: moved shortcuts and onKeyEvents to its own file by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2223 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.17...v10.5.18 - -## 10.5.17 - -* feat(l10n): localize all untranslated.json by @erdnx in https://github.com/singerdmx/flutter-quill/pull/2217 -* Fix: Block Attributes are not displayed if the editor is empty by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2210 - -## New Contributors -* @erdnx made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2217 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.16...v10.5.17 - -## 10.5.16 - -* chore: remove device_info_plus and add quill_native_bridge to access platform specific APIs by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2194 -* Not show/update/hiden mangnifier when manifier config is disbale by @demoYang in https://github.com/singerdmx/flutter-quill/pull/2212 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.14...v10.5.16 - -## 10.5.15-dev.0 - -Introduce `quill_native_bridge` which is an internal plugin to use by `flutter_quill` to access platform APIs. - -For now, the only functionality it supports is to check whatever the iOS app is running on iOS simulator without requiring [`device_info_plus`](pub.dev/packages/device_info_plus) as a dependency. - -> [!NOTE] -> `quill_native_bridge` is a plugin for internal use and should not be used in production applications -> as breaking changes can happen and can removed at any time. - -For more details and discussion see [#2194](https://github.com/singerdmx/flutter-quill/pull/2194). - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.14...v10.5.15-dev.0 - -## 10.5.14 - -* chore(localization): add Greek language support by @DKalathas in https://github.com/singerdmx/flutter-quill/pull/2206 - -## New Contributors -* @DKalathas made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2206 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.13...v10.5.14 - -## 10.5.13 - -* Revert "Fix: Allow backspace at start of document to remove block style and header style by @agata in https://github.com/singerdmx/flutter-quill/pull/2201 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.12...v10.5.13 - -## 10.5.12 - -* Fix: Backspace remove block attributes at start by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2200 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.11...v10.5.12 - -## 10.5.11 - -* Enhancement: Backspace handling at the start of blocks in delete rules by @agata in https://github.com/singerdmx/flutter-quill/pull/2199 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.10...v10.5.11 - -## 10.5.10 - -* Allow backspace at start of document to remove block style and header style by @agata in https://github.com/singerdmx/flutter-quill/pull/2198 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.9...v10.5.10 - -## 10.5.9 - -* chore: improve platform check by using constants and defaultTargetPlatform by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2188 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.8...v10.5.9 - -## 10.5.8 - -* Feat: Add configuration option to always indent on TAB key press by @agata in https://github.com/singerdmx/flutter-quill/pull/2187 - -## New Contributors -* @agata made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2187 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.7...v10.5.8 - -## 10.5.7 - -* chore(example): downgrade Kotlin from 1.9.24 to 1.7.10 by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2185 -* style: refactor build leading function style, width, and padding parameters for custom node leading builder by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2182 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.6...v10.5.7 - -## 10.5.6 - -* chore(deps): update super_clipboard to 0.8.20 in flutter_quill_extensions by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2181 -* Update quill_screen.dart, i chaged the logic for showing a lock when â€Ļ by @rightpossible in https://github.com/singerdmx/flutter-quill/pull/2183 - -## New Contributors -* @rightpossible made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2183 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.5...v10.5.6 - -## 10.5.5 - -* Fix text selection handles when scroll mobile by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2176 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.4...v10.5.5 - -## 10.5.4 - -* Add Thai (th) localization by @silkyland in https://github.com/singerdmx/flutter-quill/pull/2175 - -## New Contributors -* @silkyland made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2175 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.3...v10.5.4 - -## 10.5.3 - -* Fix: Assertion Failure in line.dart When Editing Text with Block-Level Attributes by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2174 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.2...v10.5.3 - -## 10.5.2 - -* fix(toolbar): regard showDividers in simple toolbar by @realth000 in https://github.com/singerdmx/flutter-quill/pull/2172 - -## New Contributors -* @realth000 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2172 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.1...v10.5.2 - -## 10.5.1 - -* fix drag selection extension (does not start at tap location if you are dragging quickly by @jezell in https://github.com/singerdmx/flutter-quill/pull/2170 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.5.0...v10.5.1 - -## 10.5.0 - -* Feat: custom leading builder by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2146 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.4.9...v10.5.0 - -## 10.4.9 - -* fix floating cursor not disappearing after scroll end by @vishna in https://github.com/singerdmx/flutter-quill/pull/2163 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.4.8...v10.4.9 - -## 10.4.8 - -* Fix: direction has no opposite effect if the language is rtl by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2154 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.4.7...v10.4.8 - -## 10.4.7 - -* Fix: Unable to scroll 2nd editor window by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2152 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.4.6...v10.4.7 - -## 10.4.6 - -* Handle null child query by @jezell in https://github.com/singerdmx/flutter-quill/pull/2151 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.4.5...v10.4.6 - -## 10.4.5 - -* chore!: move spell checker to example by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2145 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.4.4...v10.4.5 - -## 10.4.4 - -* fix custom recognizer builder not being passed to editabletextblock by @jezell in https://github.com/singerdmx/flutter-quill/pull/2143 -* fix null reference exception when dragging selection on non scrollable selection by @jezell in https://github.com/singerdmx/flutter-quill/pull/2144 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.4.3...v10.4.4 - -## 10.4.3 - -* Chore: update simple_spell_checker package by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2139 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.4.2...v10.4.3 - -## 10.4.2 - -* Revert "fix: Double click to select text sometimes doesn't work. ([#2086](https://github.com/singerdmx/flutter-quill/pull/2086)) - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.4.1...v10.4.2 - -## 10.4.1 - -* Chore: improve Spell checker API to the example by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2133 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.4.0...v10.4.1 - -## 10.4.0 - -* Copy TapAndPanGestureRecognizer from TextField by @demoYang in https://github.com/singerdmx/flutter-quill/pull/2128 -* enhance stringToColor with a custom defined palette from `DefaultStyles` by @vishna in https://github.com/singerdmx/flutter-quill/pull/2095 -* Feat: include spell checker for example app by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2127 - -## New Contributors -* @vishna made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2095 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.3.3...v10.4.0 - -## 10.3.2 - -* Fix: Loss of style when backspace by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2125 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.3.1...v10.3.2 - -## 10.3.1 - -* Chore: Move spellchecker service to extensions by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2120 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.3.0...v10.3.1 - -## 10.3.0 - -* Feat: Spellchecker for Flutter Quill by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2118 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.2.1...v10.3.0 - -## 10.2.1 - -* Fix: context menu is visible even when selection is collapsed by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2116 -* Fix: unsafe operation while getting overlayEntry in text_selection by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2117 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.2.0...v10.2.1 - -## 10.2.0 - -* refactor!: restructure project into modular architecture for flutter_quill_extensions by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2106 -* Fix: Link selection and editing by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2114 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.10...v10.2.0 - -## 10.1.10 - -* Fix(example): image_cropper outdated version by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2100 -* Using dart.library.js_interop instead of dart.library.html by @h1376h in https://github.com/singerdmx/flutter-quill/pull/2103 - -## New Contributors -* @h1376h made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2103 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.9...v10.1.10 - -## 10.1.9 - -* restore ability to pass in key to QuillEditor by @mtallenca in https://github.com/singerdmx/flutter-quill/pull/2093 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.8...v10.1.9 - -## 10.1.8 - -* Enhancement: Search within Embed objects by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2090 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.7...v10.1.8 - -## 10.1.7 - -* Feature/allow shortcut override by @InstrinsicAutomations in https://github.com/singerdmx/flutter-quill/pull/2089 - -## New Contributors -* @InstrinsicAutomations made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2089 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.6...v10.1.7 - -## 10.1.6 - -* fixed #1295 Double click to select text sometimes doesn't work. by @li8607 in https://github.com/singerdmx/flutter-quill/pull/2086 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.5...v10.1.6 - -## 10.1.5 - -* ref: add `VerticalSpacing.zero` and `HorizontalSpacing.zero` named constants by @adil192 in https://github.com/singerdmx/flutter-quill/pull/2083 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.4...v10.1.5 - -## 10.1.4 - -* Fix: collectStyles for lists and alignments by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2082 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.3...v10.1.4 - -## 10.1.3 - -* Move Controller outside of configurations data class by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2078 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.2...v10.1.3 - -## 10.1.2 - -* Fix Multiline paste with attributes and embeds by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2074 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.1...v10.1.2 - -## 10.1.1 - -* Toolbar dividers fixes + Docs updates by @troyanskiy in https://github.com/singerdmx/flutter-quill/pull/2071 - -## New Contributors -* @troyanskiy made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2071 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.0...v10.1.1 - -## 10.1.0 - -* Feat: support for customize copy and cut Embeddables to Clipboard by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2067 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.10...v10.1.0 - -## 10.0.10 - -* fix: Hide selection toolbar if editor loses focus by @huandu in https://github.com/singerdmx/flutter-quill/pull/2066 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.9...v10.0.10 - -## 10.0.9 - -* Fix: manual checking of directionality by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2063 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.8...v10.0.9 - -## 10.0.8 - -* feat: add callback to handle performAction by @huandu in https://github.com/singerdmx/flutter-quill/pull/2061 -* fix: Invalid selection when tapping placeholder text by @huandu in https://github.com/singerdmx/flutter-quill/pull/2062 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.7...v10.0.8 - -## 10.0.7 - -* Fix: RTL issues by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2060 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.6...v10.0.7 - -## 10.0.6 - -* fix: textInputAction is not set when creating QuillRawEditorConfiguration by @huandu in https://github.com/singerdmx/flutter-quill/pull/2057 - -## New Contributors -* @huandu made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2057 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.5...v10.0.6 - -## 10.0.5 - -* Add tests for PreserveInlineStylesRule and fix link editing. Other minor fixes. by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2058 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.4...v10.0.5 - -## 10.0.4 - -* Add ability to set up horizontal spacing for block style by @dimkanovikov in https://github.com/singerdmx/flutter-quill/pull/2051 -* add catalan language by @spilioio in https://github.com/singerdmx/flutter-quill/pull/2054 - -## New Contributors -* @dimkanovikov made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2051 -* @spilioio made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2054 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.3...v10.0.4 - -## 10.0.3 - -* doc(Delta): more documentation about Delta by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2042 -* doc(attribute): added documentation about Attribute class and how create one by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2048 -* if magnifier removes toolbar, restore it when it is hidden by @mtallenca in https://github.com/singerdmx/flutter-quill/pull/2049 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.2...v10.0.3 - -## 10.0.2 - -* chore(scripts): migrate the scripts from sh to dart by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2036 -* Have the ability to create custom rules, closes #1162 by @Guillergood in https://github.com/singerdmx/flutter-quill/pull/2040 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.1...v10.0.2 - -## 10.0.1 - -This release is identical to [10.0.0](https://github.com/singerdmx/flutter-quill/releases/tag/v10.0.0) with a fix that addresses issue #2034 by requiring `10.0.0` as the minimum version for quill related dependencies. - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.0...v10.0.1 - -## 10.0.0 - -* refactor: restructure project into modular architecture for flutter_quill by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2032 -* chore: update GitHub PR template by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2033 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.6.0...v10.0.0 - -## 9.6.0 - -* [feature] : quill add magnifier by @demoYang in https://github.com/singerdmx/flutter-quill/pull/2026 - -## New Contributors -* @demoYang made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2026 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.23...v9.6.0 - -## 9.5.23 - -* add untranslated Kurdish keys by @Xoshbin in https://github.com/singerdmx/flutter-quill/pull/2029 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.22...v9.5.23 - -## 9.5.22 - -* Fix outdated contributor guide link on PR template by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2027 -* Fix(rule): PreserveInlineStyleRule assume the type of the operation data and throw stacktrace by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2028 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.21...v9.5.22 - -## 9.5.21 - -* Fix: Key actions not being handled by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2025 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.20...v9.5.21 - -## 9.5.20 - -* Remove useless delta_x_test by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2017 -* Update flutter_quill_delta_from_html package on pubspec.yaml by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2018 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.19...v9.5.20 - -## 9.5.19 - -* fixed #1835 Embed Reloads on Cmd Key Press by @li8607 in https://github.com/singerdmx/flutter-quill/pull/2013 - -## New Contributors -* @li8607 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2013 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.18...v9.5.19 - -## 9.5.18 - -* Refactor: Moved core link button functions to link.dart by @Alspb in https://github.com/singerdmx/flutter-quill/pull/2008 -* doc: more documentation about Rules by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2014 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.17...v9.5.18 - -## 9.5.17 - -* Feat(config): added option to disable automatic list conversion by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2011 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.16...v9.5.17 - -## 9.5.16 - -* chore: drop support for HTML, PDF, and Markdown converting functions by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/1997 -* docs(readme): update the extensions package to document the Rich Text Paste feature on web by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/2001 -* Fix(test): delta_x tests fail by wrong expected Delta for video embed by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2010 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.15...v9.5.16 - -## 9.5.15 - -* Update delta_from_html to fix nested lists issues and more by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2000 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.14...v9.5.15 - -## 9.5.14 - -* docs(readme): update 'Conversion to HTML' section to include more details by @EchoEllet in https://github.com/singerdmx/flutter-quill/pull/1996 -* Update flutter_quill_delta_from_html on pubspec.yaml to fix current issues by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1999 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.13...v9.5.14 - -## 9.5.13 - -* Added new default ConverterOptions configurations by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1990 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.12...v9.5.13 - -## 9.5.12 - -* fix: Fixed passing textStyle to formula embed by @shubham030 in https://github.com/singerdmx/flutter-quill/pull/1989 - -## New Contributors -* @shubham030 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1989 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.11...v9.5.12 - -## 9.5.11 - -* Update flutter_quill_delta_from_html in pubspec.yaml by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1988 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.10...v9.5.11 - -## 9.5.10 - -* chore: remove dependency html converter by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1987 -* Fix: LineHeight button to use MenuAnchor by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1986 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.9...v9.5.10 - -## 9.5.9 - -* Update pubspec.yaml to remove html2md by @singerdmx in https://github.com/singerdmx/flutter-quill/pull/1985 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.8...v9.5.9 - -## 9.5.8 - -* fix(typo): fix typo ClipboardServiceProvider.instacne by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1983 -* Feat: New way to get Delta from HTML inputs by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1984 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.7...v9.5.8 - -## 9.5.7 - -* refactor: context menu function, add test code by @n7484443 in https://github.com/singerdmx/flutter-quill/pull/1979 -* Fix: PreserveInlineStylesRule by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1980 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.6...v9.5.7 - -## 9.5.6 - -* fix: common link is detected as a video link by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1978 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.5...v9.5.6 - -## 9.5.5 - -* fix: context menu behavior in mouse, desktop env by @n7484443 in https://github.com/singerdmx/flutter-quill/pull/1976 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.4...v9.5.5 - -## 9.5.4 - -* Feat: Line height support by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1972 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.3...v9.5.4 - -## 9.5.3 - -* Perf: Performance optimization by @Alspb in https://github.com/singerdmx/flutter-quill/pull/1964 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.2...v9.5.3 - -## 9.5.2 - -* Fix style settings by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1962 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.1...v9.5.2 - -## 9.5.1 - -* feat(extensions): Youtube Video Player Support Mode by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1916 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.0...v9.5.1 - -## 9.5.0 - -* Partial support for table embed by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1960 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.9...v9.5.0 - -## 9.4.9 - -* Upgrade photo_view to 0.15.0 for flutter_quill_extensions by @singerdmx in https://github.com/singerdmx/flutter-quill/pull/1958 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.8...v9.4.9 - -## 9.4.8 - -* Add support for html underline and videos by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1955 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.7...v9.4.8 - -## 9.4.7 - -* fixed #1953 italic detection error by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1954 - -## New Contributors -* @CatHood0 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1954 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.6...v9.4.7 - -## 9.4.6 - -* fix: search dialog throw an exception due to missing FlutterQuillLocalizations.delegate in the editor by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1938 -* fix(editor): implement editor shortcut action for home and end keys to fix exception about unimplemented ScrollToDocumentBoundaryIntent by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1937 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.5...v9.4.6 - -## 9.4.5 - -* fix: color picker hex unfocus on web by @geronimol in https://github.com/singerdmx/flutter-quill/pull/1934 - -## New Contributors -* @geronimol made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1934 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.4...v9.4.5 - -## 9.4.4 - -* fix: Enabled link regex to be overridden by @JoepHeijnen in https://github.com/singerdmx/flutter-quill/pull/1931 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.3...v9.4.4 - -## 9.4.3 - -* Fix: setState() called after dispose(): QuillToolbarClipboardButtonState #1895 by @windows7lake in https://github.com/singerdmx/flutter-quill/pull/1926 - -## New Contributors -* @windows7lake made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1926 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.2...v9.4.3 - -## 9.4.2 - -* Respect autofocus, closes #1923 by @Guillergood in https://github.com/singerdmx/flutter-quill/pull/1924 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.1...v9.4.2 - -## 9.4.1 - -* replace base64 regex string by @salba360496 in https://github.com/singerdmx/flutter-quill/pull/1919 - -## New Contributors -* @salba360496 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1919 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.0...v9.4.1 - -## 9.4.0 - -This release can be used without changing anything, although it can break the behavior a little, we provided a way to use the old behavior in `9.3.x` - -- Thanks to @Alspb, the search bar/dialog has been reworked for improved UI that fits **Material 3** look and feel, the search happens on the fly, and other minor changes, if you want the old search bar, you can restore it with one line if you're using `QuillSimpleToolbar`: - ```dart - QuillToolbar.simple( - configurations: QuillSimpleToolbarConfigurations( - searchButtonType: SearchButtonType.legacy, - ), - ) - ``` - While the changes are mostly to the `QuillToolbarSearchDialog` and it seems this should be `searchDialogType`, we provided the old button with the old dialog in case we update the button in the future. - - If you're using `QuillToolbarSearchButton` in a custom Toolbar, you don't need anything to get the new button. if you want the old button, use the `QuillToolbarLegacySearchButton` widget - - Consider using the improved button with the improved dialog as the legacy button might removed in future releases (for now, it's not deprecated) - -
- Before - - ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/9b40ad03-717f-4518-95f1-8d9cad773b2b) - - -
- -
- Improved - - ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/e581733d-63fa-4984-9c41-4a325a0a0c04) - -
- - For the detailed changes, see #1904 - -- Korean translations by @leegh519 in https://github.com/singerdmx/flutter-quill/pull/1911 - -- The usage of `super_clipboard` plugin in `flutter_quill` has been moved to the `flutter_quill_extensions` package, this will restore the old behavior in `8.x.x` though it will break the `onImagePaste`, `onGifPaste` and rich text pasting from HTML or Markdown, most of those features are available in `super_clipboard` plugin except `onImagePaste` which was available as we were using [pasteboard](https://pub.dev/packages/pasteboard), Unfortunately, it's no longer supported on recent versions of Flutter, and some functionalities such as an image from Clipboard and Html paste are not supported on some platforms such as Android, your project will continue to work, calls of `onImagePaste` and `onGifPaste` will be ignored unless you include [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions) package in your project and call: - - ```dart - FlutterQuillExtensions.useSuperClipboardPlugin(); - ``` - Before using any `flutter_quill` widgets, this will restore the old behavior in `9.x.x` - - We initially wanted to publish `flutter_quill_super_clipboard` to allow: - - Using `super_clipboard` without `flutter_quill_extensions` packages and plugins - - Using `flutter_quill_extensions` with optional `super_clipboard` - - To simplify the usage, we moved it to `flutter_quill_extensions`, let us know if you want any of the use cases above. - - Overall `super_clipboard` is a Comprehensive clipboard plugin with a lot of features, the only thing that developers didn't want is Rust installation even though it's automated. - - The main goal of `ClipboardService` is to make `super_clipboard` optional, you can use your own implementation, and create a class that implements `ClipboardService`, which you can get by: - ```dart - // ignore: implementation_imports - import 'package:flutter_quill/src/services/clipboard/clipboard_service.dart'; - ``` - - Then you can call: - ```dart - // ignore: implementation_imports -import 'package:flutter_quill/src/services/clipboard/clipboard_service_provider.dart'; - ClipboardServiceProvider.setInstance(YourClipboardService()); -``` - - The interface could change at any time and will be updated internally for `flutter_quill` and `flutter_quill_extensions`, we didn't export those two classes by default to avoid breaking changes in case you use them as we might change them in the future. - - If you use the above imports, you might get **breaking changes** in **non-breaking change releases**. - -- Subscript and Superscript should now work for all languages and characters - - The previous implementation required the Apple 'SF-Pro-Display-Regular.otf' font which is only licensed/permitted for use on Apple devices. -We have removed the Apple font from the example - -- Allow pasting Markdown and HTML file content from the system to the editor - - Before `9.4.x` if you try to copy an HTML or Markdown file, and paste it into the editor, you will get the file name in the editor - Copying an HTML file, or HTML content from apps and websites is different than copying plain text. - - This is why this change requires `super_clipboard` implementation as this is platform-dependent: - ```dart - FlutterQuillExtensions.useSuperClipboardPlugin(); - ``` - as mentioned above. - - The following example for copying a Markdown file: - -
- Markdown File Content - - ```md - - **Note**: This package supports converting from HTML back to Quill delta but it's experimental and used internally when pasting HTML content from the clipboard to the Quill Editor - - You have two options: - - 1. Using [quill_html_converter](./quill_html_converter/) to convert to HTML, the package can convert the Quill delta to HTML well - (it uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html)), it is just a handy extension to do it more quickly - 1. Another option is to use - [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) to convert your document - to HTML. - This package has full support for all Quill operations—including images, videos, formulas, - tables, and mentions. - Conversion can be performed in vanilla Dart (i.e., server-side or CLI) or in Flutter. - It is a complete Dart part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) - Typescript/Javascript package. - this package doesn't convert the HTML back to Quill Delta as far as we know - - ``` - -
- -
- Before - - ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/03f5ae20-796c-4e8b-8668-09a994211c1e) - -
- -
- After - - ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/7e3a1987-36e7-4665-944a-add87d24e788) - -
- - Markdown, and HTML converting from and to Delta are **currently far from perfect**, the current implementation could improved a lot - however **it will likely not work like expected**, due to differences between HTML and Delta, see this [comment](https://github.com/slab/quill/issues/1551#issuecomment-311458570) for more info. - - ![Copying Markdown file into Flutter Quill Editor](https://github.com/singerdmx/flutter-quill/assets/73608287/63bd6ba6-cc49-4335-84dc-91a0fa5c95a9) - - For more details see #1915 - - Using or converting to HTML or Markdown is highly experimental and shouldn't be used for production applications. - - We use it internally as it is more suitable for our specific use case., copying content from external websites and pasting it into the editor - previously breaks the styles, while the current implementation is not ready, it provides a better user experience and doesn't have many downsides. - - Feel free to report any bugs or feature requests at [Issues](https://github.com/singerdmx/flutter-quill/issues) or drop any suggestions and questions at [Discussions](https://github.com/singerdmx/flutter-quill/discussions) - -## New Contributors -* @leegh519 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1911 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.21...v9.4.0 - -## 9.3.21 - -* fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.20...v9.3.21 - -## 9.3.20 - -* Fix: Issue 1887 by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1892 -* fix: toolbar style change will be invalid when inputting more than 2 characters at a time by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1890 - -## New Contributors -* @crasowas made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1890 - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.19...v9.3.20 - -## 9.3.19 - -* Fix reported issues by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1886 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.18...v9.3.19 - -## 9.3.18 - -* Fix: Undo/redo cursor position fixed by @Alspb in https://github.com/singerdmx/flutter-quill/pull/1885 - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.17...v9.3.18 - -## 9.3.17 - -* Update super_clipboard plugin to 0.8.15 to address [#1882](https://github.com/singerdmx/flutter-quill/issues/1882) - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.16...v9.3.17 - -## 9.3.16 - -* Update `lint` dev package to 4.0.0 -* Require at least version 0.8.13 of the plugin - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.15...v9.3.16 - -## 9.3.15 - - -* Ci/automate updating the files by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1879 -* Updating outdated README.md and adding a few guidelines for CONTRIBUTING.md - - -**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.14...v9.3.15 - -## 9.3.14 - -* Chore/use original color picker package in [#1877](https://github.com/singerdmx/flutter-quill/pull/1877) - -## 9.3.13 - -* fix: `readOnlyMouseCursor` losing in construction function -* Fix block multi-line selection style - -## 9.3.12 - -* Add `readOnlyMouseCursor` to config mouse cursor type - -## 9.3.11 - -* Fix typo in QuillHtmlConverter -* Fix re-create checkbox - -## 9.3.10 - -* Support clipboard actions from the toolbar - -## 9.3.9 - -* fix: MD Parsing for multi space -* fix: FontFamily and FontSize toolbars track the text selected in the editor -* feat: Add checkBoxReadOnly property which can override readOnly for checkbox - -## 9.3.8 - -* fix: removed misleading parameters -* fix: added missed translations for ru, es, de -* added translations for Nepali Locale('ne', 'NP') - -## 9.3.7 - -* Fix for keyboard jumping when switching focus from a TextField -* Toolbar button styling to reflect cursor position when running on desktops with keyboard to move care - -## 9.3.6 - -* Add SK and update CS locales [#1796](https://github.com/singerdmx/flutter-quill/pull/1796) -* Fixes: - * QuillIconTheme changes for FontFamily and FontSize buttons are not applied [#1797](https://github.com/singerdmx/flutter-quill/pull/1796) - * Make the arrow_drop_down icons in the QuillToolbar the same size for all MenuAnchor buttons [#1799](https://github.com/singerdmx/flutter-quill/pull/1796) - -## 9.3.5 - -* Update the minimum version for the packages to support `device_info_plus` version 10.0.0 [#1783](https://github.com/singerdmx/flutter-quill/issues/1783) -* Update the minimum version for `youtube_player_flutter` to new major version 9.0.0 in the `flutter_quill_extensions` - -## 9.3.4 - -* fix: multiline styling stuck/not working properly [#1782](https://github.com/singerdmx/flutter-quill/pull/1782) - -## 9.3.3 - -* Update `quill_html_converter` versions - -## 9.3.2 - -* Fix dispose of text painter [#1774](https://github.com/singerdmx/flutter-quill/pull/1774) - -## 9.3.1 - -* Require Flutter 3.19.0 as minimum version - -## 9.3.0 - -* **Breaking change**: `Document.fromHtml(html)` is now returns `Document` instead of `Delta`, use `DeltaX.fromHtml` to return `Delta` -* Update old deprecated api from Flutter 3.19 -* Scribble scroll fix by @mtallenca in https://github.com/singerdmx/flutter-quill/pull/1745 - -## 9.2.14 - -* feat: move cursor after inserting video/image -* Apple pencil - -## 9.2.13 - -* Fix crash with inserting text from contextMenuButtonItems -* Fix incorrect behaviour of context menu -* fix: selection handles behaviour and unnessesary style assert -* Update quill_fr.arb - -## 9.2.12 - -* Fix safari clipboard bug -* Add the option to disable clipboard functionality - -## 9.2.11 - -* Fix a bug where it has problems with pasting text into the editor when the clipboard has styled text - -## 9.2.10 - -* Update example screenshots -* Refactor `Container` to `QuillContainer` with backward compatibility -* A workaround fix in history feature - -## 9.2.9 - -* Refactor the type of `Delta().toJson()` to be more clear type - -## 9.2.8 - -* feat: Export Container node as QuillContainer -* fix web cursor position / height (don't use iOS logic) -* Added Swedish translation - -## 9.2.6 - -* [fix selection.affinity always downstream after updateEditingValue](https://github.com/singerdmx/flutter-quill/pull/1682) -* Bumb version of `super_clipboard` - -## 9.2.5 - -* Bumb version of `super_clipboard` - -## 9.2.4 - -* Use fixed version of intl - -## 9.2.3 - -* remove unncessary column in Flutter quill video embed block - -## 9.2.2 - -* Fix bug [#1627](https://github.com/singerdmx/flutter-quill/issues/1627) - -## 9.2.1 - -* Fix [bug](https://github.com/singerdmx/flutter-quill/issues/1119#issuecomment-1872605246) with font size button -* Added ro RO translations -* 📖 Update zh, zh_CN translations - -## 9.2.0 - -* Require minimum version `6.0.0` of `flutter_keyboard_visibility` to fix some build issues with Android Gradle Plugin 8.2.0 -* Add on image clicked in `flutter_quill_extensions` callback -* Deprecate `globalIconSize` and `globalIconButtonFactor`, use `iconSize` and `iconButtonFactor` instead -* Fix the `QuillToolbarSelectAlignmentButtons` - -## 9.1.1 - -* Require `super_clipboard` minimum version `0.8.1` to fix some bug with Linux build failure - -## 9.1.1-dev - -* Fix bug [#1636](https://github.com/singerdmx/flutter-quill/issues/1636) -* Fix a where you paste styled content (HTML) it always insert a new line at first even if the document is empty -* Fix the font size button and migrate to `MenuAnchor` -* The `defaultDisplayText` is no longer required in the font size and header dropdown buttons -* Add pdf converter in a new package (`quill_pdf_converter`) - -## 9.1.0 - -* Fix the simple toolbar by add properties of `IconButton` and fix some buttons - -## 9.1.0-dev.2 - -* Fix the history buttons - -## 9.1.0-dev.1 - -* Bug fixes in the simple toolbar buttons - -## 9.1.0-dev - -* **Breaking Change**: in the `QuillSimpleToolbar` Fix the `QuillIconTheme` by replacing all the properties with two properties of type `ButtonStyle`, use `IconButton.styleFrom()` - -## 9.0.6 - -* Fix bug in QuillToolbarSelectAlignmentButtons - -## 9.0.5 - -* You can now use most of the buttons without internal provider - -## 9.0.4 - -* Feature: [#1611](https://github.com/singerdmx/flutter-quill/issues/1611) -* Export missing widgets - -## 9.0.3 - -* Flutter Quill Extensions: - * Fix file image support for web image emebed builder - -## 9.0.2 - -* Remove unused properties in the `QuillToolbarSelectHeaderStyleDropdownButton` -* Fix the `QuillSimpleToolbar` when `useMaterial3` is false, please upgrade to the latest version of flutter for better support - -## 9.0.2-dev.3 - -* Export `QuillSingleChildScrollView` - -## 9.0.2-dev.2 - -* Add the new translations for ru, uk arb files by [#1575](https://github.com/singerdmx/flutter-quill/pull/1575) -* Add a new dropdown button by [#1575](https://github.com/singerdmx/flutter-quill/pull/1575) -* Update the default style values by [#1575](https://github.com/singerdmx/flutter-quill/pull/1575) -* Fix bug [#1562](https://github.com/singerdmx/flutter-quill/issues/1562) -* Fix the second bug of [#1480](https://github.com/singerdmx/flutter-quill/issues/1480) - -## 9.0.2-dev.1 - -* Add configurations for the new dropdown `QuillToolbarSelectHeaderStyleButton`, you can use the orignal one or this -* Fix the [issue](https://github.com/singerdmx/flutter-quill/issues/1119) when enter is pressed, all font settings is lost - -## 9.0.2-dev - -* **Breaking change** Remove the spacer widget, removed the controller option for each button -* Add `toolbarRunSpacing` property to the simple toolbar - -## 9.0.1 - -* Fix default icon size - -## 9.0.0 - -* This version is quite stable but it's not how we wanted to be, because the lack of time and there are not too many maintainers active, we decided to publish it, we might make a new breaking changes verion - -## 9.0.1-dev.1 - -* Flutter Quill Extensions: - * Update `QuillImageUtilities` and fixining some bugs - -## 9.0.1-dev - -* Test new GitHub workflows - -## 9.0.0-dev-10 - -* Fix a bug of the improved pasting HTML contents contents into the editor - -## 9.0.0-dev-9 - -* Improves the new logic of pasting HTML contents into the Editor -* Update `README.md` and the doc -* Dispose the `QuillToolbarSelectHeaderStyleButton` state listener in `dispose` -* Upgrade the font family button to material 3 -* Rework the font family and font size functionalities to change the font once and type all over the editor - -## 9.0.0-dev-8 - -* Better support for pasting HTML contents from external websites to the editor -* The experimental support of converting the HTML from `quill_html_converter` is now built-in in the `flutter_quill` and removed from there (Breaking change for `quill_html_converter`) - -## 9.0.0-dev-7 - -* Fix a bug in chaning the background/font color of ol/ul list -* Flutter Quill Extensions: - * Fix link bug in the video url - * Fix patterns - -## 9.0.0-dev-6 - -* Move the `child` from `QuillToolbarConfigurations` into `QuillToolbar` directly -* Bug fixes -* Add the ability to change the background and font color of the ol/ul elements dots and numbers -* Flutter Quill Extensions: - * **Breaking Change**: The `imageProviderBuilder`is now providing the context and image url - -## 9.0.0-dev-5 - -* The `QuillToolbar` is now accepting only `child` with no configurations so you can customize everything you wants, the `QuillToolbar.simple()` or `QuillSimpleToolbar` implements a simple toolbar that is based on `QuillToolbar`, you are free to use it but it just an example and not standard -* Flutter Quill Extensions: - * Improve the camera button - -## 9.0.0-dev-4 - -* The options parameter in all of the buttons is no longer required which can be useful to create custom toolbar with minimal efforts -* Toolbar buttons fixes in both `flutter_quill` and `flutter_quill_extensions` -* The `QuillProvider` has been dropped and no longer used, the providers will be used only internally from now on and we will not using them as much as possible - -## 9.0.0-dev-3 - -* Breaking Changes: - * Rename `QuillToolbar` to `QuillSimpleToolbar` - * Rename `QuillBaseToolbar` to `QuillToolbar` - * Replace `pasteboard` with `rich_cliboard` -* Fix a bug in the example when inserting an image from url -* Flutter Quill Extensions: - * Add support for copying the image to the system cliboard - -## 9.0.0-dev-2 - -* An attemp to fix CI automated publishing - -## 9.0.0-dev-1 - -* An attemp to fix CI automated publishing - -## 9.0.0-dev - -* **Major Breaking change**: The `QuillProvider` is now optional, the `controller` parameter has been moved to the `QuillEditor` and `QuillToolbar` once again. -* Flutter Quill Extensions; - * **Breaking Change**: Completly change the way how the source code structured to more basic and simple way, organize folders and file names, if you use the library -from `flutter_quill_extensions.dart` then there is nothing you need to do, but if you are using any other import then you need to re-imports -embed, this won't affect how quill js work - * Improvemenets to the image embed - * Add support for `margin` for web - * Add untranslated strings to the `quill_en.arb` - -## 8.6.4 - -* The default value of `keyboardAppearance` for the iOS will be the one from the App/System theme mode instead of always using the `Brightness.light` -* Fix typos in `README.md` - -## 8.6.3 - -* Update the minimum flutter version to `3.16.0` - -## 8.6.2 - -* Restore use of alternative QuillToolbarLinkStyleButton2 widget - -## 8.6.1 - -* Temporary revert style bug fix - -## 8.6.0 - -* **Breaking Change** Support [Flutter 3.16](https://medium.com/flutter/whats-new-in-flutter-3-16-dba6cb1015d1), please upgrade to the latest stable version of flutter to use this update -* **Breaking Change**: Remove Deprecated Fields -* **Breaking Change**: Extract the shared things between `QuillToolbarConfigurations` and `QuillBaseToolbarConfigurations` -* **Breaking Change**: You no longer need to use `QuillToolbarProvider` when using custom toolbar buttons, the example has been updated -* Bug fixes - -## 8.5.5 - -* Now when opening dialogs by `QuillToolbar` you will not get an exception when you don't use `FlutterQuillLocalizations.delegate` in your `WidgetsApp`, `MaterialApp`, or `CupertinoApp`. The fix is for the `QuillToolbarSearchButton`, `QuillToolbarLinkStyleButton`, and `QuillToolbarColorButton` buttons - -## 8.5.4 - -* The `mobileWidth`, `mobileHeight`, `mobileMargin`, and `mobileAlignment` is now deprecated in `flutter_quill`, they are now defined in `flutter_quill_extensions` -* Deprecate `replaceStyleStringWithSize` function which is in `string.dart` -* Deprecate `alignment`, and `margin` as they don't conform to official Quill JS - -## 8.5.3 - -* Update doc -* Update `README.md` and `CHANGELOG.md` -* Fix typos -* Use `immutable` when possible -* Update `.pubignore` - -## 8.5.2 - -* Updated `README.md`. -* Feature: Added the ability to include a custom callback when the `QuillToolbarColorButton` is pressed. -* The `QuillToolbar` now implements `PreferredSizeWidget`, enabling usage in the AppBar, similar to `QuillBaseToolbar`. - -## 8.5.1 - -* Updated `README.md`. - -## 8.5.0 - -* Migrated to `flutter_localizations` for translations. -* Fixed: Translated all previously untranslated localizations. -* Fixed: Added translations for missing items. -* Fixed: Introduced default Chinese fallback translation. -* Removed: Unused parameters `items` in `QuillToolbarFontFamilyButtonOptions` and `QuillToolbarFontSizeButtonOptions`. -* Updated: Documentation. - -## 8.4.4 - -* Updated `.pubignore` to ignore unnecessary files and folders. - -## 8.4.3 - -* Updated `CHANGELOG.md`. - -## 8.4.2 - -* **Breaking change**: Configuration for `QuillRawEditor` has been moved to a separate class. Additionally, `readOnly` has been renamed to `isReadOnly`. If using `QuillEditor`, no action is required. -* Introduced the ability for developers to override `TextInputAction` in both `QuillRawEditor` and `QuillEditor`. -* Enabled using `QuillRawEditor` without `QuillEditorProvider`. -* Bug fixes. -* Added image cropping implementation in the example. - -## 8.4.1 - -* Added `copyWith` in `OptionalSize` class. - -## 8.4.0 - -* **Breaking change**: Updated `QuillCustomButton` to use `QuillCustomButtonOptions`. Moved all properties from `QuillCustomButton` to `QuillCustomButtonOptions`, replacing `iconData` with `icon` widget for increased customization. -* **Breaking change**: `customButtons` in `QuillToolbarConfigurations` is now of type `List`. -* Bug fixes following the `8.0.0` update. -* Updated `README.md`. -* Improved platform checking. - -## 8.3.0 - -* Added `iconButtonFactor` property to `QuillToolbarBaseButtonOptions` for customizing button size relative to its icon size (defaults to `kIconButtonFactor`, consistent with previous releases). - -## 8.2.6 - -* Organized `QuillRawEditor` code. - -## 8.2.5 - -* Added `builder` property in `QuillEditorConfigurations`. - -## 8.2.4 - -* Adhered to Flutter best practices. -* Fixed auto-focus bug. - -## 8.2.3 - -* Updated `README.md`. - -## 8.2.2 - -* Moved `flutter_quill_test` to a separate package: [flutter_quill_test](https://pub.dev/packages/flutter_quill_test). - -## 8.2.1 - -* Updated `README.md`. - -## 8.2.0 - -* Added the option to add configurations for `flutter_quill_extensions` using `extraConfigurations`. - -## 8.1.11 - -* Followed Dart best practices by using `lints` and removed `pedantic` and `platform` since they are not used. -* Fixed text direction bug. -* Updated `README.md`. - -## 8.1.10 - -* Secret for automated publishing to pub.dev. - -## 8.1.9 - -* Fixed automated publishing to pub.dev. - -## 8.1.8 - -* Fixed automated publishing to pub.dev. - -## 8.1.7 - -* Automated publishing to pub.dev. - -## 8.1.6 - -* Fixed compatibility with `integration_test` by downgrading the minimum version of the platform package to 3.1.0. - -## 8.1.5 - -* Reversed background/font color toolbar button icons. - -## 8.1.4 - -* Reversed background/font color toolbar button tooltips. - -## 8.1.3 - -* Moved images to screenshots instead of `README.md`. - -## 8.1.2 - -* Fixed a bug related to the regexp of the insert link dialog. -* Required Dart 3 as the minimum version. -* Code cleanup. -* Added a spacer widget between each button in the `QuillToolbar`. - -## 8.1.1 - -* Fixed null error in line.dart #1487(https://github.com/singerdmx/flutter*quill/issues/1487). - -## 8.1.0 - -* Fixed a word typo of `mirgration` to `migration` in the readme & migration document. -* Updated migration guide. -* Removed property `enableUnfocusOnTapOutside` in `QuillEditor` configurations and added `isOnTapOutsideEnabled` instead. -* Added a new callback called `onTapOutside` in the `QuillEditorConfigurations` to perform actions when tapping outside the editor. -* Fixed a bug that caused the web platform to not unfocus the editor when tapping outside of it. To override this, please pass a value to the `onTapOutside` callback. -* Removed the old property of `iconTheme`. Instead, pass `iconTheme` in the button options; you will find the `base` property inside it with `iconTheme`. - -## 8.0.0 - -* If you have migrated recently, don't be alarmed by this update; it adds documentation, a migration guide, and marks the version as a more stable release. Although there are breaking changes (as reported by some developers), the major version was not changed due to time constraints during development. A single property was also renamed from `code` to `codeBlock` in the `elements` of the new `QuillEditorConfigurations` class. -* Updated the README for better readability. - -## 7.10.2 - -* Removed line numbers from code blocks by default. You can still enable this feature thanks to the new configurations in the `QuillEditor`. Find the `elementOptions` property and enable `enableLineNumbers`. - -## 7.10.1 - -* Fixed issues and utilized the new parameters. -* No longer need to use `MaterialApp` for most toolbar button child builders. -* Compatibility with [fresh_quill_extensions](https://pub.dev/packages/fresh_quill_extensions), a temporary alternative to [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions). -* Updated most of the documentation in `README.md`. - -## 7.10.0 - -* **Breaking change**: `QuillToolbar.basic()` can be accessed directly from `QuillToolbar()`, and the old `QuillToolbar` can be accessed from `QuillBaseToolbar`. -* Refactored Quill editor and toolbar configurations into a single class each. -* After changing checkbox list values, the controller will not request keyboard focus by default. -* Moved toolbar and editor configurations directly into the widget but still use inherited widgets internally. -* Fixes to some code after the refactoring. - -## 7.9.0 - -* Buttons Improvemenets -* Refactor all the button configurations that used in `QuillToolbar.basic()` but there are still few lefts -* **Breaking change**: Remove some configurations from the QuillToolbar and move them to the new `QuillProvider`, please notice this is a development version and this might be changed in the next few days, the stable release will be ready in less than 3 weeks -* Update `flutter_quill_extensions` and it will be published into pub.dev soon. -* Allow you to customize the search dialog by custom callback with child builder - -## 7.8.0 - -* **Important note**: this is not test release yet, it works but need more test and changes and breaking changes, we don't have development version and it will help us if you try the latest version and report the issues in Github but if you want a stable version please use `7.4.16`. this refactoring process will not take long and should be done less than three weeks with the testing. -* We managed to refactor most of the buttons configurations and customizations in the `QuillProvider`, only three lefts then will start on refactoring the toolbar configurations -* Code improvemenets - -## 7.7.0 - -* **Breaking change**: We have mirgrated more buttons in the toolbar configurations, you can do change them in the `QuillProvider` -* Important bug fixes - -## 7.6.1 - -* Bug fixes - -## 7.6.0 - -* **Breaking change**: To customize the buttons in the toolbar, you can do that in the `QuillProvider` - -## 7.5.0 - -* **Breaking change**: The widgets `QuillEditor` and `QuillToolbar` are no longer have controller parameter, instead you need to make sure in the widget tree you have wrapped them with `QuillProvider` widget and provide the controller and the require configurations - -## 7.4.16 - -* Update documentation and README.md - -## 7.4.15 - -* Custom style attrbuites for platforms other than mobile (alignment, margin, width, height) -* Bug fixes and other improvemenets - -## 7.4.14 - -* Improve performance by reducing the number of widgets rebuilt by listening to media query for only the needed things, for example instead of using `MediaQuery.of(context).size`, now we are using `MediaQuery.sizeOf(context)` -* Add MediaButton for picking the images only since the video one is not ready -* A new feature which allows customizing the text selection in quill editor which is useful for custom theme design system for custom app widget - -## 7.4.13 - -* Fixed tab editing when in readOnly mode. - -## 7.4.12 - -* Update the minimum version of device_info_plus to 9.1.0. - -## 7.4.11 - -* Add sw locale. - -## 7.4.10 - -* Update translations. - -## 7.4.9 - -* Style recognition fixes. - -## 7.4.8 - -* Upgrade dependencies. - -## 7.4.7 - -* Add Vietnamese and German translations. - -## 7.4.6 - -* Fix more null errors in Leaf.retain [##1394](https://github.com/singerdmx/flutter-quill/issues/1394) and Line.delete [##1395](https://github.com/singerdmx/flutter-quill/issues/1395). - -## 7.4.5 - -* Fix null error in Container.insert [##1392](https://github.com/singerdmx/flutter-quill/issues/1392). - -## 7.4.4 - -* Fix extra padding on checklists [##1131](https://github.com/singerdmx/flutter-quill/issues/1131). - -## 7.4.3 - -* Fixed a space input error on iPad. - -## 7.4.2 - -* Fix bug with keepStyleOnNewLine for link. - -## 7.4.1 - -* Fix toolbar dividers condition. - -## 7.4.0 - -* Support Flutter version 3.13.0. - -## 7.3.3 - -* Updated Dependencies conflicting. - -## 7.3.2 - -* Added builder for custom button in _LinkDialog. - -## 7.3.1 - -* Added case sensitive and whole word search parameters. -* Added wrap around. -* Moved search dialog to the bottom in order not to override the editor and the text found. -* Other minor search dialog enhancements. - -## 7.3.0 - -* Add default attributes to basic factory. - -## 7.2.19 - -* Feat/link regexp. - -## 7.2.18 - -* Fix paste block text in words apply same style. - -## 7.2.17 - -* Fix paste text mess up style. -* Add support copy/cut block text. - -## 7.2.16 - -* Allow for custom context menu. - -## 7.2.15 - -* Add flutter_quill.delta library which only exposes Delta datatype. - -## 7.2.14 - -* Fix errors when the editor is used in the `screenshot` package. - -## 7.2.13 - -* Fix around image can't delete line break. - -## 7.2.12 - -* Add support for copy/cut select image and text together. - -## 7.2.11 - -* Add affinity for localPosition. - -## 7.2.10 - -* LINE._getPlainText queryChild inclusive=false. - -## 7.2.9 - -* Add toPlainText method to `EmbedBuilder`. - -## 7.2.8 - -* Add custom button widget in toolbar. - -## 7.2.7 - -* Fix language code of Japan. - -## 7.2.6 - -* Style custom toolbar buttons like builtins. - -## 7.2.5 - -* Always use text cursor for editor on desktop. - -## 7.2.4 - -* Fixed keepStyleOnNewLine. - -## 7.2.3 - -* Get pixel ratio from view. - -## 7.2.2 - -* Prevent operations on stale editor state. - -## 7.2.1 - -* Add support for android keyboard content insertion. -* Enhance color picker, enter hex color and color palette option. - -## 7.2.0 - -* Checkboxes, bullet points, and number points are now scaled based on the default paragraph font size. - -## 7.1.20 - -* Pass linestyle to embedded block. - -## 7.1.19 - -* Fix Rtl leading alignment problem. - -## 7.1.18 - -* Support flutter latest version. - -## 7.1.17+1 - -* Updates `device_info_plus` to version 9.0.0 to benefit from AGP 8 (see [changelog##900](https://pub.dev/packages/device_info_plus/changelog##900)). - -## 7.1.16 - -* Fixed subscript key from 'sup' to 'sub'. - -## 7.1.15 - -* Fixed a bug introduced in 7.1.7 where each section in `QuillToolbar` was displayed on its own line. - -## 7.1.14 - -* Add indents change for multiline selection. - -## 7.1.13 - -* Add custom recognizer. - -## 7.1.12 - -* Add superscript and subscript styles. - -## 7.1.11 - -* Add inserting indents for lines of list if text is selected. - -## 7.1.10 - -* Image embedding tweaks - * Add MediaButton which is intened to superseed the ImageButton and VideoButton. Only image selection is working. - * Implement image insert for web (image as base64) - -## 7.1.9 - -* Editor tweaks PR from bambinoua(https://github.com/bambinoua). - * Shortcuts now working in Mac OS - * QuillDialogTheme is extended with new properties buttonStyle, linkDialogConstraints, imageDialogConstraints, isWrappable, runSpacing, - * Added LinkStyleButton2 with new LinkStyleDialog (similar to Quill implementation - * Conditinally use Row or Wrap for dialog's children. - * Update minimum Dart SDK version to 2.17.0 to use enum extensions. - * Use merging shortcuts and actions correclty (if the key combination is the same) - -## 7.1.8 - -* Dropdown tweaks - * Add itemHeight, itemPadding, defaultItemColor for customization of dropdown items. - * Remove alignment property as useless. - * Fix bugs with max width when width property is null. - -## 7.1.7 - -* Toolbar tweaks. - * Implement tooltips for embed CameraButton, VideoButton, FormulaButton, ImageButton. - * Extends customization for SelectAlignmentButton, QuillFontFamilyButton, QuillFontSizeButton adding padding, text style, alignment, width. - * Add renderFontFamilies to QuillFontFamilyButton to show font faces in dropdown. - * Add AxisDivider and its named constructors for for use in parent project. - * Export ToolbarButtons enum to allow specify tooltips for SelectAlignmentButton. - * Export QuillFontFamilyButton, SearchButton as they were not exported before. - * Deprecate items property in QuillFontFamilyButton, QuillFontSizeButton as the it can be built usinr rawItemsMap. - * Make onSelection QuillFontFamilyButton, QuillFontSizeButton omittable as no need to execute callback outside if controller is passed to widget. - -Now the package is more friendly for web projects. - -## 7.1.6 - -* Add enableUnfocusOnTapOutside field to RawEditor and Editor widgets. - -## 7.1.5 - -* Add tooltips for toolbar buttons. - -## 7.1.4 - -* Fix inserting tab character in lists. - -## 7.1.3 - -* Fix ios cursor bug when word.length==1. - -## 7.1.2 - -* Fix non scrollable editor exception, when tapped under content. - -## 7.1.1 - -* customLinkPrefixes parameter * makes possible to open links with custom protoco. - -## 7.1.0 - -* Fix ordered list numeration with several lists in document. - -## 7.0.9 - -* Use const constructor for EmbedBuilder. - -## 7.0.8 - -* Fix IME position bug with scroller. - -## 7.0.7 - -* Add TextFieldTapRegion for contextMenu. - -## 7.0.6 - -* Fix line style loss on new line from non string. - -## 7.0.5 - -* Fix IME position bug for Mac and Windows. -* Unfocus when tap outside editor. fix the bug that cant refocus in afterButtonPressed after click ToggleStyleButton on Mac. - -## 7.0.4 - -* Have text selection span full line height for uneven sized text. - -## 7.0.3 - -* Fix ordered list numeration for lists with more than one level of list. - -## 7.0.2 - -* Allow widgets to override widget span properties. - -## 7.0.1 - -* Update i18n_extension dependency to version 8.0.0. - -## 7.0.0 - -* Breaking change: Tuples are no longer used. They have been replaced with a number of data classes. - -## 6.4.4 - -* Increased compatibility with Flutter widget tests. - -## 6.4.3 - -* Update dependencies (collection: 1.17.0, flutter_keyboard_visibility: 5.4.0, quiver: 3.2.1, tuple: 2.0.1, url_launcher: 6.1.9, characters: 1.2.1, i18n_extension: 7.0.0, device_info_plus: 8.1.0) - -## 6.4.2 - -* Replace `buildToolbar` with `contextMenuBuilder`. - -## 6.4.1 - -* Control the detect word boundary behaviour. - -## 6.4.0 - -* Use `axis` to make the toolbar vertical. -* Use `toolbarIconCrossAlignment` to align the toolbar icons on the cross axis. -* Breaking change: `QuillToolbar`'s parameter `toolbarHeight` was renamed to `toolbarSize`. - -## 6.3.5 - -* Ability to add custom shortcuts. - -## 6.3.4 - -* Update clipboard status prior to showing selected text overlay. - -## 6.3.3 - -* Fixed handling of mac intents. - -## 6.3.2 - -* Added `unknownEmbedBuilder` to QuillEditor. -* Fix error style when input chinese japanese or korean. - -## 6.3.1 - -* Add color property to the basic factory function. - -## 6.3.0 - -* Support Flutter 3.7. - -## 6.2.2 - -* Fix: nextLine getter null where no assertion. - -## 6.2.1 - -* Revert "Align numerical and bullet lists along with text content". - -## 6.2.0 - -* Align numerical and bullet lists along with text content. - -## 6.1.12 - -* Apply i18n for default font dropdown option labels corresponding to 'Clear'. - -## 6.1.11 - -* Remove iOS hack for delaying focus calculation. - -## 6.1.10 - -* Delay focus calculation for iOS. - -## 6.1.9 - -* Bump keyboard show up wait to 1 sec. - -## 6.1.8 - -* Recalculate focus when showing keyboard. - -## 6.1.7 - -* Add czech localizations. - -## 6.1.6 - -* Upgrade i18n_extension to 6.0.0. - -## 6.1.5 - -* Fix formatting exception. - -## 6.1.4 - -* Add double quotes validation. - -## 6.1.3 - -* Revert "fix order list numbering (##988)". - -## 6.1.2 - -* Add typing shortcuts. - -## 6.1.1 - -* Fix order list numbering. - -## 6.1.0 - -* Add keyboard shortcuts for editor actions. - -## 6.0.10 - -* Upgrade device info plus to ^7.0.0. - -## 6.0.9 - -* Don't throw showAutocorrectionPromptRect not implemented. The function is called with every keystroke as a user is typing. - -## 6.0.8+1 - -* Fixes null pointer when setting documents. - -## 6.0.8 - -* Make QuillController.document mutable. - -## 6.0.7 - -* Allow disabling of selection toolbar. - -## 6.0.6+1 - -* Revert 6.0.6. - -## 6.0.6 - -* Fix wrong custom embed key. - -## 6.0.5 - -* Fixes toolbar buttons stealing focus from editor. - -## 6.0.4 - -* Bug fix for Type 'Uint8List' not found. - -## 6.0.3 - -* Add ability to paste images. - -## 6.0.2 - -* Address Dart Analysis issues. - -## 6.0.1 - -* Changed translation country code (zh_HK -> zh_hk) to lower case, which is required for i18n_extension used in flutter_quill. -* Add localization in example's main to demonstrate translation. -* Issue Windows selection's copy / paste tool bar not shown ##861: add selection's copy / paste toolbar, escape to hide toolbar, mouse right click to show toolbar, ctrl-Y / ctrl-Z to undo / redo. -* Image and video displayed in Windows platform caused screen flickering while selecting text, a sample_data_nomedia.json asset is added for Desktop to demonstrate the added features. -* Known issue: keyboard action sometimes causes exception mentioned in Flutter's issue ##106475 (Windows Keyboard shortcuts stop working after modifier key repeat flutter/flutter##106475). -* Know issue: user needs to click the editor to get focus before toolbar is able to display. - -## 6.0.0 BREAKING CHANGE - -* Removed embed (image, video & formula) blocks from the package to reduce app size. - -These blocks have been moved to the package `flutter_quill_extensions`, migrate by filling the `embedBuilders` and `embedButtons` parameters as follows: - -``` -import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; - -QuillEditor.basic( - controller: controller, - embedBuilders: FlutterQuillEmbeds.builders(), -); - -QuillToolbar.basic( - controller: controller, - embedButtons: FlutterQuillEmbeds.buttons(), -); -``` - -## 5.4.2 - -* Upgrade i18n_extension. - -## 5.4.1 - -* Update German Translation. - -## 5.4.0 - -* Added Formula Button (for maths support). - -## 5.3.2 - -* Add more font family. - -## 5.3.1 - -* Enable search when text is not empty. - -## 5.3.0 - -* Added search function. - -## 5.2.11 - -* Remove default small color. - -## 5.2.10 - -* Don't wrap the QuillEditor's child in the EditorTextSelectionGestureDetector if selection is disabled. - -## 5.2.9 - -* Added option to modify SelectHeaderStyleButton options. -* Added option to click again on h1, h2, h3 button to go back to normal. - -## 5.2.8 - -* Remove tooltip for LinkStyleButton. -* Make link match regex case insensitive. - -## 5.2.7 - -* Add locale to QuillEditor.basic. - -## 5.2.6 - -* Fix keyboard pops up when resizing the image. - -## 5.2.5 - -* Upgrade youtube_player_flutter_quill to 8.2.2. - -## 5.2.4 - -* Upgrade youtube_player_flutter_quill to 8.2.1. - -## 5.2.3 - -* Flutter Quill Doesn't Work On iOS 16 or Xcode 14 Betas (Stored properties cannot be marked potentially unavailable with '@available'). - -## 5.2.2 - -* Fix Web Unsupported operation: Platform.\_operatingSystem error. - -## 5.2.1 - -* Rename QuillCustomIcon to QuillCustomButton. - -## 5.2.0 - -* Support font family selection. - -## 5.1.1 - -* Update README. - -## 5.1.0 - -* Added CustomBlockEmbed and customElementsEmbedBuilder. - -## 5.0.5 - -* Upgrade device_info_plus to 4.0.0. - -## 5.0.4 - -* Added onVideoInit callback for video documents. - -## 5.0.3 - -* Update dependencies. - -## 5.0.2 - -* Keep cursor position on checkbox tap. - -## 5.0.1 - -* Fix static analysis errors. - -## 5.0.0 - -* Flutter 3.0.0 support. - -## 4.2.3 - -* Ignore color:inherit and convert double to int for level. - -## 4.2.2 - -* Add clear option to font size dropdown. - -## 4.2.1 - -* Refactor font size dropdown. - -## 4.2.0 - -* Ensure selectionOverlay is available for showToolbar. - -## 4.1.9 - -* Using properly iconTheme colors. - -## 4.1.8 - -* Update font size dropdown. - -## 4.1.7 - -* Convert FontSize to a Map to allow for named Font Size. - -## 4.1.6 - -* Update quill_dropdown_button.dart. - -## 4.1.5 - -* Add Font Size dropdown to the toolbar. - -## 4.1.4 - -* New borderRadius for iconTheme. - -## 4.1.3 - -* Fix selection handles show/hide after paste, backspace, copy. - -## 4.1.2 - -* Add full support for hardware keyboards (Chromebook, Android tablets, etc) that don't alter screen UI. - -## 4.1.1 - -* Added textSelectionControls field in QuillEditor. - -## 4.1.0 - -* Added Node to linkActionPickerDelegate. - -## 4.0.12 - -* Add Persian(fa) language. - -## 4.0.11 - -* Fix cut selection error in multi-node line. - -## 4.0.10 - -* Fix vertical caret position bug. - -## 4.0.9 - -* Request keyboard focus when no child is found. - -## 4.0.8 - -* Fix blank lines do not display when **web*renderer=html. - -## 4.0.7 - -* Refactor getPlainText (better handling of blank lines and lines with multiple markups. - -## 4.0.6 - -* Bug fix for copying text with new lines. - -## 4.0.5 - -* Fixed casting null to Tuple2 when link dialog is dismissed without any input (e.g. barrier dismissed). - -## 4.0.4 - -* Bug fix for text direction rtl. - -## 4.0.3 - -* Support text direction rtl. - -## 4.0.2 - -* Clear toggled style on selection change. - -## 4.0.1 - -* Fix copy/cut/paste/selectAll not working. - -## 4.0.0 - -* Upgrade for Flutter 2.10. - -## 3.9.11 - -* Added Indonesian translation. - -## 3.9.10 - -* Fix for undoing a modification ending with an indented line. - -## 3.9.9 - -* iOS: Save image whose filename does not end with image file extension. - -## 3.9.8 - -* Added Urdu translation. - -## 3.9.7 - -* Fix for clicking on the Link button without any text on a new line crashes. - -## 3.9.6 - -* Apply locale to QuillEditor(contents). - -## 3.9.5 - -* Fix image pasting. - -## 3.9.4 - -* Hiding dialog after selecting action for image. - -## 3.9.3 - -* Update ImageResizer for Android. - -## 3.9.2 - -* Copy image with its style. - -## 3.9.1 - -* Support resizing image. - -## 3.9.0 - -* Image menu options for copy/remove. - -## 3.8.8 - -* Update set textEditingValue. - -## 3.8.7 - -* Fix checkbox not toggled correctly in toolbar button. - -## 3.8.6 - -* Fix cursor position changes when checking/unchecking the checkbox. - -## 3.8.5 - -* Fix \_handleDragUpdate in \_TextSelectionHandleOverlayState. - -## 3.8.4 - -* Fix link dialog layout. - -## 3.8.3 - -* Fix for errors on a non scrollable editor. - -## 3.8.2 - -* Fix certain keys not working on web when editor is a child of a scroll view. - -## 3.8.1 - -* Refactor \_QuillEditorState to QuillEditorState. - -## 3.8.0 - -* Support pasting with format. - -## 3.7.3 - -* Fix selection overlay for collapsed selection. - -## 3.7.2 - -* Reverted Embed toPlainText change. - -## 3.7.1 - -* Change Embed toPlainText to be empty string. - -## 3.7.0 - -* Replace Toolbar showHistory group with individual showRedo and showUndo. - -## 3.6.5 - -* Update Link dialogue for image/video. - -## 3.6.4 - -* Link dialogue TextInputType.multiline. - -## 3.6.3 - -* Bug fix for link button text selection. - -## 3.6.2 - -* Improve link button. - -## 3.6.1 - -* Remove SnackBar 'What is entered is not a link'. - -## 3.6.0 - -* Allow link button to enter text. - -## 3.5.3 - -* Change link button behavior. - -## 3.5.2 - -* Bug fix for embed. - -## 3.5.1 - -* Bug fix for platform util. - -## 3.5.0 - -* Removed redundant classes. - -## 3.4.4 - -* Add more translations. - -## 3.4.3 - -* Preset link from attributes. - -## 3.4.2 - -* Fix launch link edit mode. - -## 3.4.1 - -* Placeholder effective in scrollable. - -## 3.4.0 - -* Option to save image in read-only mode. - -## 3.3.1 - -* Pass any specified key in QuillEditor constructor to super. - -## 3.3.0 - -* Fixed Style toggle issue. - -## 3.2.1 - -* Added new translations. - -## 3.2.0 - -* Support multiple links insertion on the go. - -## 3.1.1 - -* Add selection completed callback. - -## 3.1.0 - -* Fixed image ontap functionality. - -## 3.0.4 - -* Add maxContentWidth constraint to editor. - -## 3.0.3 - -* Do not show caret on screen when the editor is not focused. - -## 3.0.2 - -* Fix launch link for read-only mode. - -## 3.0.1 - -* Handle null value of Attribute.link. - -## 3.0.0 - -* Launch link improvements. -* Removed QuillSimpleViewer. - -## 2.5.2 - -* Skip image when pasting. - -## 2.5.1 - -* Bug fix for Desktop `Shift` + `Click` support. - -## 2.5.0 - -* Update checkbox list. - -## 2.4.1 - -* Desktop selection improvements. - -## 2.4.0 - -* Improve inline code style. - -## 2.3.3 - -* Improves selection rects to have consistent height regardless of individual segment text styles. - -## 2.3.2 - -* Allow disabling floating cursor. - -## 2.3.1 - -* Preserve last newline character on delete. - -## 2.3.0 - -* Massive changes to support flutter 2.8. - -## 2.2.2 - -* iOS - floating cursor. - -## 2.2.1 - -* Bug fix for imports supporting flutter 2.8. - -## 2.2.0 - -* Support flutter 2.8. - -## 2.1.1 - -* Add methods of clearing editor and moving cursor. - -## 2.1.0 - -* Add delete handler. - -## 2.0.23 - -* Support custom replaceText handler. - -## 2.0.22 - -* Fix attribute compare and fix font size parsing. - -## 2.0.21 - -* Handle click on embed object. - -## 2.0.20 - -* Improved UX/UI of Image widget. - -## 2.0.19 - -* When uploading a video, applying indicator. - -## 2.0.18 - -* Make toolbar dividers optional. - -## 2.0.17 - -* Allow alignment of the toolbar icons to match WrapAlignment. - -## 2.0.16 - -* Add hide / show alignment buttons. - -## 2.0.15 - -* Implement change cursor to SystemMouseCursors.click when hovering a link styled text. - -## 2.0.14 - -* Enable customize the checkbox widget using DefaultListBlockStyle style. - -## 2.0.13 - -* Improve the scrolling performance by reducing the repaint areas. - -## 2.0.12 - -* Fix the selection effect can't be seen as the textLine with background color. - -## 2.0.11 - -* Fix visibility of text selection handlers on scroll. - -## 2.0.10 - -* cursorConnt.color notify the text_line to repaint if it was disposed. - -## 2.0.9 - -* Improve UX when trying to add a link. - -## 2.0.8 - -* Adding translations to the toolbar. - -## 2.0.7 - -* Added theming options for toolbar icons and LinkDialog. - -## 2.0.6 - -* Avoid runtime error when placed inside TabBarView. - -## 2.0.5 - -* Support inline code formatting. - -## 2.0.4 - -* Enable history shortcuts for desktop. - -## 2.0.3 - -* Fix cursor when line contains image. - -## 2.0.2 - -* Address KeyboardListener class name conflict. - -## 2.0.1 - -* Upgrade flutter_colorpicker to 0.5.0. - -## 2.0.0 - -* Text Alignment functions + Block Format standards. - -## 1.9.6 - -* Support putting QuillEditor inside a Scrollable view. - -## 1.9.5 - -* Skip image when pasting. - -## 1.9.4 - -* Bug fix for cursor position when tapping at the end of line with image(s). - -## 1.9.3 - -* Bug fix when line only contains one image. - -## 1.9.2 - -* Support for building custom inline styles. - -## 1.9.1 - -* Cursor jumps to the most appropriate offset to display selection. - -## 1.9.0 - -* Support inline image. - -## 1.8.3 - -* Updated quill_delta. - -## 1.8.2 - -* Support mobile image alignment. - -## 1.8.1 - -* Support mobile custom size image. - -## 1.8.0 - -* Support entering link for image/video. - -## 1.7.3 - -* Bumps photo_view version. - -## 1.7.2 - -* Fix static analysis error. - -## 1.7.1 - -* Support Youtube video. - -## 1.7.0 - -* Support video. - -## 1.6.4 - -* Bug fix for clear format button. - -## 1.6.3 - -* Fixed dragging right handle scrolling issue. - -## 1.6.2 - -* Fixed the position of the selection status drag handle. - -## 1.6.1 - -* Upgrade image_picker and flutter_colorpicker. - -## 1.6.0 - -* Support Multi Row Toolbar. - -## 1.5.0 - -* Remove file_picker dependency. - -## 1.4.1 - -* Remove filesystem_picker dependency. - -## 1.4.0 - -* Remove path_provider dependency. - -## 1.3.4 - -* Add option to paintCursorAboveText. - -## 1.3.3 - -* Upgrade file_picker version. - -## 1.3.2 - -* Fix copy/paste bug. - -## 1.3.1 - -* New logo. - -## 1.3.0 - -* Support flutter 2.2.0. - -## 1.2.2 - -* Checkbox supports tapping. - -## 1.2.1 - -* Indented position not holding while editing. - -## 1.2.0 - -* Fix image button cancel causes crash. - -## 1.1.8 - -* Fix height of empty line bug. - -## 1.1.7 - -* Fix text selection in read-only mode. - -## 1.1.6 - -* Remove universal_html dependency. - -## 1.1.5 - -* Enable "Select", "Select All" and "Copy" in read-only mode. - -## 1.1.4 - -* Fix text selection issue. - -## 1.1.3 - -* Update example folder. - -## 1.1.2 - -* Add pedantic. - -## 1.1.1 - -* Base64 image support. - -## 1.1.0 - -* Support null safety. - -## 1.0.9 - -* Web support for raw editor and keyboard listener. - -## 1.0.8 - -* Support token attribute. - -## 1.0.7 - -* Fix crash on web (dart:io). - -## 1.0.6 - -* Add desktop support WINDOWS, MACOS and LINUX. - -## 1.0.5 - -* Bug fix: Can not insert newline when Bold is toggled ON. - -## 1.0.4 - -* Upgrade photo_view to ^0.11.0. - -## 1.0.3 - -* Fix issue that text is not displayed while typing WEB. - -## 1.0.2 - -* Update toolbar in sample home page. - -## 1.0.1 - -* Fix static analysis errors. - -## 1.0.0 - -* Support flutter 2.0. - -## 1.0.0-dev.2 - -* Improve link handling for tel, mailto and etc. - -## 1.0.0-dev.1 - -* Upgrade prerelease SDK & Bump for master. - -## 0.3.5 - -* Fix for cursor focus issues when keyboard is on. - -## 0.3.4 - -* Improve link handling for tel, mailto and etc. - -## 0.3.3 - -* More fix on cursor focus issue when keyboard is on. - -## 0.3.2 - -* Fix cursor focus issue when keyboard is on. - -## 0.3.1 - -* cursor focus when keyboard is on. - -## 0.3.0 - -* Line Height calculated based on font size. - -## 0.2.12 - -* Support placeholder. - -## 0.2.11 - -* Fix static analysis error. - -## 0.2.10 - -* Update TextInputConfiguration autocorrect to true in stable branch. - -## 0.2.9 - -* Update TextInputConfiguration autocorrect to true. - -## 0.2.8 - -* Support display local image besides network image in stable branch. - -## 0.2.7 - -* Support display local image besides network image. - -## 0.2.6 - -* Fix cursor after pasting. - -## 0.2.5 - -* Toggle text/background color button in toolbar. - -## 0.2.4 - -* Support the use of custom icon size in toolbar. - -## 0.2.3 - -* Support custom styles and image on local device storage without uploading. - -## 0.2.2 - -* Update git repo. - -## 0.2.1 - -* Fix static analysis error. - -## 0.2.0 - -* Add checked/unchecked list button in toolbar. - -## 0.1.8 - -* Support font and size attributes. - -## 0.1.7 - -* Support checked/unchecked list. - -## 0.1.6 - -* Fix getExtentEndpointForSelection. - -## 0.1.5 - -* Support text alignment. - -## 0.1.4 - -* Handle url with trailing spaces. - -## 0.1.3 - -* Handle cursor position change when undo/redo. - -## 0.1.2 - -* Handle more text colors. - -## 0.1.1 - -* Fix cursor issue when undo. - -## 0.1.0 - -* Fix insert image. - -## 0.0.9 - -* Handle rgba color. - -## 0.0.8 - -* Fix launching url. - -## 0.0.7 - -* Handle multiple image inserts. - -## 0.0.6 - -* More toolbar functionality. - -## 0.0.5 - -* Update example. - -## 0.0.4 - -* Update example. - -## 0.0.3 - -* Update home page meta data. - -## 0.0.2 - -* Support image upload and launch url in read-only mode. - -## 0.0.1 - -* Rich text editor based on Quill Delta. - diff --git a/quill_native_bridge/.gitignore b/quill_native_bridge/quill_native_bridge/.gitignore similarity index 100% rename from quill_native_bridge/.gitignore rename to quill_native_bridge/quill_native_bridge/.gitignore diff --git a/quill_native_bridge/.metadata b/quill_native_bridge/quill_native_bridge/.metadata similarity index 100% rename from quill_native_bridge/.metadata rename to quill_native_bridge/quill_native_bridge/.metadata diff --git a/quill_native_bridge/quill_native_bridge/CHANGELOG.md b/quill_native_bridge/quill_native_bridge/CHANGELOG.md new file mode 100644 index 000000000..b90c7c674 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## 10.7.4 + +- diff --git a/quill_native_bridge/LICENSE b/quill_native_bridge/quill_native_bridge/LICENSE similarity index 100% rename from quill_native_bridge/LICENSE rename to quill_native_bridge/quill_native_bridge/LICENSE diff --git a/quill_native_bridge/README.md b/quill_native_bridge/quill_native_bridge/README.md similarity index 100% rename from quill_native_bridge/README.md rename to quill_native_bridge/quill_native_bridge/README.md diff --git a/quill_native_bridge/analysis_options.yaml b/quill_native_bridge/quill_native_bridge/analysis_options.yaml similarity index 100% rename from quill_native_bridge/analysis_options.yaml rename to quill_native_bridge/quill_native_bridge/analysis_options.yaml diff --git a/quill_native_bridge/android/.gitignore b/quill_native_bridge/quill_native_bridge/android/.gitignore similarity index 100% rename from quill_native_bridge/android/.gitignore rename to quill_native_bridge/quill_native_bridge/android/.gitignore diff --git a/quill_native_bridge/android/build.gradle b/quill_native_bridge/quill_native_bridge/android/build.gradle similarity index 100% rename from quill_native_bridge/android/build.gradle rename to quill_native_bridge/quill_native_bridge/android/build.gradle diff --git a/quill_native_bridge/android/settings.gradle b/quill_native_bridge/quill_native_bridge/android/settings.gradle similarity index 100% rename from quill_native_bridge/android/settings.gradle rename to quill_native_bridge/quill_native_bridge/android/settings.gradle diff --git a/quill_native_bridge/android/src/main/AndroidManifest.xml b/quill_native_bridge/quill_native_bridge/android/src/main/AndroidManifest.xml similarity index 100% rename from quill_native_bridge/android/src/main/AndroidManifest.xml rename to quill_native_bridge/quill_native_bridge/android/src/main/AndroidManifest.xml diff --git a/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt similarity index 100% rename from quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt rename to quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt diff --git a/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardImageHandler.kt b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardImageHandler.kt similarity index 100% rename from quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardImageHandler.kt rename to quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardImageHandler.kt diff --git a/quill_native_bridge/example/.gitignore b/quill_native_bridge/quill_native_bridge/example/.gitignore similarity index 100% rename from quill_native_bridge/example/.gitignore rename to quill_native_bridge/quill_native_bridge/example/.gitignore diff --git a/quill_native_bridge/example/README.md b/quill_native_bridge/quill_native_bridge/example/README.md similarity index 100% rename from quill_native_bridge/example/README.md rename to quill_native_bridge/quill_native_bridge/example/README.md diff --git a/quill_native_bridge/example/analysis_options.yaml b/quill_native_bridge/quill_native_bridge/example/analysis_options.yaml similarity index 100% rename from quill_native_bridge/example/analysis_options.yaml rename to quill_native_bridge/quill_native_bridge/example/analysis_options.yaml diff --git a/quill_native_bridge/example/android/.gitignore b/quill_native_bridge/quill_native_bridge/example/android/.gitignore similarity index 100% rename from quill_native_bridge/example/android/.gitignore rename to quill_native_bridge/quill_native_bridge/example/android/.gitignore diff --git a/quill_native_bridge/example/android/app/build.gradle b/quill_native_bridge/quill_native_bridge/example/android/app/build.gradle similarity index 100% rename from quill_native_bridge/example/android/app/build.gradle rename to quill_native_bridge/quill_native_bridge/example/android/app/build.gradle diff --git a/quill_native_bridge/example/android/app/src/debug/AndroidManifest.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from quill_native_bridge/example/android/app/src/debug/AndroidManifest.xml rename to quill_native_bridge/quill_native_bridge/example/android/app/src/debug/AndroidManifest.xml diff --git a/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from quill_native_bridge/example/android/app/src/main/AndroidManifest.xml rename to quill_native_bridge/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml diff --git a/quill_native_bridge/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt similarity index 100% rename from quill_native_bridge/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt rename to quill_native_bridge/quill_native_bridge/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt diff --git a/quill_native_bridge/example/android/app/src/main/res/drawable-v21/launch_background.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from quill_native_bridge/example/android/app/src/main/res/drawable-v21/launch_background.xml rename to quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/quill_native_bridge/example/android/app/src/main/res/drawable/launch_background.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from quill_native_bridge/example/android/app/src/main/res/drawable/launch_background.xml rename to quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/quill_native_bridge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from quill_native_bridge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/quill_native_bridge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from quill_native_bridge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/quill_native_bridge/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from quill_native_bridge/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/quill_native_bridge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from quill_native_bridge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/quill_native_bridge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from quill_native_bridge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/quill_native_bridge/example/android/app/src/main/res/values-night/styles.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/values-night/styles.xml similarity index 100% rename from quill_native_bridge/example/android/app/src/main/res/values-night/styles.xml rename to quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/values-night/styles.xml diff --git a/quill_native_bridge/example/android/app/src/main/res/values/styles.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from quill_native_bridge/example/android/app/src/main/res/values/styles.xml rename to quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/values/styles.xml diff --git a/quill_native_bridge/example/android/app/src/main/res/xml/file_paths.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/xml/file_paths.xml similarity index 100% rename from quill_native_bridge/example/android/app/src/main/res/xml/file_paths.xml rename to quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/xml/file_paths.xml diff --git a/quill_native_bridge/example/android/app/src/profile/AndroidManifest.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from quill_native_bridge/example/android/app/src/profile/AndroidManifest.xml rename to quill_native_bridge/quill_native_bridge/example/android/app/src/profile/AndroidManifest.xml diff --git a/quill_native_bridge/example/android/build.gradle b/quill_native_bridge/quill_native_bridge/example/android/build.gradle similarity index 100% rename from quill_native_bridge/example/android/build.gradle rename to quill_native_bridge/quill_native_bridge/example/android/build.gradle diff --git a/quill_native_bridge/example/android/gradle.properties b/quill_native_bridge/quill_native_bridge/example/android/gradle.properties similarity index 100% rename from quill_native_bridge/example/android/gradle.properties rename to quill_native_bridge/quill_native_bridge/example/android/gradle.properties diff --git a/quill_native_bridge/example/android/gradle/wrapper/gradle-wrapper.properties b/quill_native_bridge/quill_native_bridge/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from quill_native_bridge/example/android/gradle/wrapper/gradle-wrapper.properties rename to quill_native_bridge/quill_native_bridge/example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/quill_native_bridge/example/android/settings.gradle b/quill_native_bridge/quill_native_bridge/example/android/settings.gradle similarity index 100% rename from quill_native_bridge/example/android/settings.gradle rename to quill_native_bridge/quill_native_bridge/example/android/settings.gradle diff --git a/quill_native_bridge/example/assets/flutter-quill.png b/quill_native_bridge/quill_native_bridge/example/assets/flutter-quill.png similarity index 100% rename from quill_native_bridge/example/assets/flutter-quill.png rename to quill_native_bridge/quill_native_bridge/example/assets/flutter-quill.png diff --git a/quill_native_bridge/example/ios/.gitignore b/quill_native_bridge/quill_native_bridge/example/ios/.gitignore similarity index 100% rename from quill_native_bridge/example/ios/.gitignore rename to quill_native_bridge/quill_native_bridge/example/ios/.gitignore diff --git a/quill_native_bridge/example/ios/Flutter/AppFrameworkInfo.plist b/quill_native_bridge/quill_native_bridge/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from quill_native_bridge/example/ios/Flutter/AppFrameworkInfo.plist rename to quill_native_bridge/quill_native_bridge/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/quill_native_bridge/example/ios/Flutter/Debug.xcconfig b/quill_native_bridge/quill_native_bridge/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from quill_native_bridge/example/ios/Flutter/Debug.xcconfig rename to quill_native_bridge/quill_native_bridge/example/ios/Flutter/Debug.xcconfig diff --git a/quill_native_bridge/example/ios/Flutter/Release.xcconfig b/quill_native_bridge/quill_native_bridge/example/ios/Flutter/Release.xcconfig similarity index 100% rename from quill_native_bridge/example/ios/Flutter/Release.xcconfig rename to quill_native_bridge/quill_native_bridge/example/ios/Flutter/Release.xcconfig diff --git a/quill_native_bridge/example/ios/Podfile b/quill_native_bridge/quill_native_bridge/example/ios/Podfile similarity index 100% rename from quill_native_bridge/example/ios/Podfile rename to quill_native_bridge/quill_native_bridge/example/ios/Podfile diff --git a/quill_native_bridge/example/ios/Podfile.lock b/quill_native_bridge/quill_native_bridge/example/ios/Podfile.lock similarity index 100% rename from quill_native_bridge/example/ios/Podfile.lock rename to quill_native_bridge/quill_native_bridge/example/ios/Podfile.lock diff --git a/quill_native_bridge/example/ios/Runner.xcodeproj/project.pbxproj b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from quill_native_bridge/example/ios/Runner.xcodeproj/project.pbxproj rename to quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.pbxproj diff --git a/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/quill_native_bridge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from quill_native_bridge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/quill_native_bridge/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from quill_native_bridge/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/quill_native_bridge/example/ios/Runner/AppDelegate.swift b/quill_native_bridge/quill_native_bridge/example/ios/Runner/AppDelegate.swift similarity index 100% rename from quill_native_bridge/example/ios/Runner/AppDelegate.swift rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/AppDelegate.swift diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/quill_native_bridge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from quill_native_bridge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/quill_native_bridge/example/ios/Runner/Base.lproj/Main.storyboard b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from quill_native_bridge/example/ios/Runner/Base.lproj/Main.storyboard rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/quill_native_bridge/example/ios/Runner/Info.plist b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Info.plist similarity index 100% rename from quill_native_bridge/example/ios/Runner/Info.plist rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Info.plist diff --git a/quill_native_bridge/example/ios/Runner/Runner-Bridging-Header.h b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from quill_native_bridge/example/ios/Runner/Runner-Bridging-Header.h rename to quill_native_bridge/quill_native_bridge/example/ios/Runner/Runner-Bridging-Header.h diff --git a/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/quill_native_bridge/example/lib/main.dart similarity index 100% rename from quill_native_bridge/example/lib/main.dart rename to quill_native_bridge/quill_native_bridge/example/lib/main.dart diff --git a/quill_native_bridge/example/macos/.gitignore b/quill_native_bridge/quill_native_bridge/example/macos/.gitignore similarity index 100% rename from quill_native_bridge/example/macos/.gitignore rename to quill_native_bridge/quill_native_bridge/example/macos/.gitignore diff --git a/quill_native_bridge/example/macos/Flutter/Flutter-Debug.xcconfig b/quill_native_bridge/quill_native_bridge/example/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from quill_native_bridge/example/macos/Flutter/Flutter-Debug.xcconfig rename to quill_native_bridge/quill_native_bridge/example/macos/Flutter/Flutter-Debug.xcconfig diff --git a/quill_native_bridge/example/macos/Flutter/Flutter-Release.xcconfig b/quill_native_bridge/quill_native_bridge/example/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from quill_native_bridge/example/macos/Flutter/Flutter-Release.xcconfig rename to quill_native_bridge/quill_native_bridge/example/macos/Flutter/Flutter-Release.xcconfig diff --git a/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift b/quill_native_bridge/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift similarity index 100% rename from quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift rename to quill_native_bridge/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift diff --git a/quill_native_bridge/example/macos/Podfile b/quill_native_bridge/quill_native_bridge/example/macos/Podfile similarity index 100% rename from quill_native_bridge/example/macos/Podfile rename to quill_native_bridge/quill_native_bridge/example/macos/Podfile diff --git a/quill_native_bridge/example/macos/Podfile.lock b/quill_native_bridge/quill_native_bridge/example/macos/Podfile.lock similarity index 100% rename from quill_native_bridge/example/macos/Podfile.lock rename to quill_native_bridge/quill_native_bridge/example/macos/Podfile.lock diff --git a/quill_native_bridge/example/macos/Runner.xcodeproj/project.pbxproj b/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/project.pbxproj similarity index 100% rename from quill_native_bridge/example/macos/Runner.xcodeproj/project.pbxproj rename to quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/project.pbxproj diff --git a/quill_native_bridge/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from quill_native_bridge/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/quill_native_bridge/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from quill_native_bridge/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/quill_native_bridge/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from quill_native_bridge/example/macos/Runner.xcworkspace/contents.xcworkspacedata rename to quill_native_bridge/quill_native_bridge/example/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/quill_native_bridge/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from quill_native_bridge/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to quill_native_bridge/quill_native_bridge/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/quill_native_bridge/example/macos/Runner/AppDelegate.swift b/quill_native_bridge/quill_native_bridge/example/macos/Runner/AppDelegate.swift similarity index 100% rename from quill_native_bridge/example/macos/Runner/AppDelegate.swift rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/AppDelegate.swift diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/quill_native_bridge/example/macos/Runner/Base.lproj/MainMenu.xib b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from quill_native_bridge/example/macos/Runner/Base.lproj/MainMenu.xib rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Base.lproj/MainMenu.xib diff --git a/quill_native_bridge/example/macos/Runner/Configs/AppInfo.xcconfig b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from quill_native_bridge/example/macos/Runner/Configs/AppInfo.xcconfig rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/AppInfo.xcconfig diff --git a/quill_native_bridge/example/macos/Runner/Configs/Debug.xcconfig b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from quill_native_bridge/example/macos/Runner/Configs/Debug.xcconfig rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Debug.xcconfig diff --git a/quill_native_bridge/example/macos/Runner/Configs/Release.xcconfig b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from quill_native_bridge/example/macos/Runner/Configs/Release.xcconfig rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Release.xcconfig diff --git a/quill_native_bridge/example/macos/Runner/Configs/Warnings.xcconfig b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from quill_native_bridge/example/macos/Runner/Configs/Warnings.xcconfig rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Warnings.xcconfig diff --git a/quill_native_bridge/example/macos/Runner/DebugProfile.entitlements b/quill_native_bridge/quill_native_bridge/example/macos/Runner/DebugProfile.entitlements similarity index 100% rename from quill_native_bridge/example/macos/Runner/DebugProfile.entitlements rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/DebugProfile.entitlements diff --git a/quill_native_bridge/example/macos/Runner/Info.plist b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Info.plist similarity index 100% rename from quill_native_bridge/example/macos/Runner/Info.plist rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Info.plist diff --git a/quill_native_bridge/example/macos/Runner/MainFlutterWindow.swift b/quill_native_bridge/quill_native_bridge/example/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from quill_native_bridge/example/macos/Runner/MainFlutterWindow.swift rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/MainFlutterWindow.swift diff --git a/quill_native_bridge/example/macos/Runner/Release.entitlements b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Release.entitlements similarity index 100% rename from quill_native_bridge/example/macos/Runner/Release.entitlements rename to quill_native_bridge/quill_native_bridge/example/macos/Runner/Release.entitlements diff --git a/quill_native_bridge/example/pubspec.yaml b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml similarity index 100% rename from quill_native_bridge/example/pubspec.yaml rename to quill_native_bridge/quill_native_bridge/example/pubspec.yaml diff --git a/quill_native_bridge/example/web/favicon.png b/quill_native_bridge/quill_native_bridge/example/web/favicon.png similarity index 100% rename from quill_native_bridge/example/web/favicon.png rename to quill_native_bridge/quill_native_bridge/example/web/favicon.png diff --git a/quill_native_bridge/example/web/icons/Icon-192.png b/quill_native_bridge/quill_native_bridge/example/web/icons/Icon-192.png similarity index 100% rename from quill_native_bridge/example/web/icons/Icon-192.png rename to quill_native_bridge/quill_native_bridge/example/web/icons/Icon-192.png diff --git a/quill_native_bridge/example/web/icons/Icon-512.png b/quill_native_bridge/quill_native_bridge/example/web/icons/Icon-512.png similarity index 100% rename from quill_native_bridge/example/web/icons/Icon-512.png rename to quill_native_bridge/quill_native_bridge/example/web/icons/Icon-512.png diff --git a/quill_native_bridge/example/web/icons/Icon-maskable-192.png b/quill_native_bridge/quill_native_bridge/example/web/icons/Icon-maskable-192.png similarity index 100% rename from quill_native_bridge/example/web/icons/Icon-maskable-192.png rename to quill_native_bridge/quill_native_bridge/example/web/icons/Icon-maskable-192.png diff --git a/quill_native_bridge/example/web/icons/Icon-maskable-512.png b/quill_native_bridge/quill_native_bridge/example/web/icons/Icon-maskable-512.png similarity index 100% rename from quill_native_bridge/example/web/icons/Icon-maskable-512.png rename to quill_native_bridge/quill_native_bridge/example/web/icons/Icon-maskable-512.png diff --git a/quill_native_bridge/example/web/index.html b/quill_native_bridge/quill_native_bridge/example/web/index.html similarity index 100% rename from quill_native_bridge/example/web/index.html rename to quill_native_bridge/quill_native_bridge/example/web/index.html diff --git a/quill_native_bridge/example/web/manifest.json b/quill_native_bridge/quill_native_bridge/example/web/manifest.json similarity index 100% rename from quill_native_bridge/example/web/manifest.json rename to quill_native_bridge/quill_native_bridge/example/web/manifest.json diff --git a/quill_native_bridge/ios/.gitignore b/quill_native_bridge/quill_native_bridge/ios/.gitignore similarity index 100% rename from quill_native_bridge/ios/.gitignore rename to quill_native_bridge/quill_native_bridge/ios/.gitignore diff --git a/quill_native_bridge/ios/Assets/.gitkeep b/quill_native_bridge/quill_native_bridge/ios/Assets/.gitkeep similarity index 100% rename from quill_native_bridge/ios/Assets/.gitkeep rename to quill_native_bridge/quill_native_bridge/ios/Assets/.gitkeep diff --git a/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift similarity index 100% rename from quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift rename to quill_native_bridge/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift diff --git a/quill_native_bridge/ios/Resources/PrivacyInfo.xcprivacy b/quill_native_bridge/quill_native_bridge/ios/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from quill_native_bridge/ios/Resources/PrivacyInfo.xcprivacy rename to quill_native_bridge/quill_native_bridge/ios/Resources/PrivacyInfo.xcprivacy diff --git a/quill_native_bridge/ios/quill_native_bridge.podspec b/quill_native_bridge/quill_native_bridge/ios/quill_native_bridge.podspec similarity index 100% rename from quill_native_bridge/ios/quill_native_bridge.podspec rename to quill_native_bridge/quill_native_bridge/ios/quill_native_bridge.podspec diff --git a/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart similarity index 96% rename from quill_native_bridge/lib/quill_native_bridge.dart rename to quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart index 52a6bcd68..42fffff5b 100644 --- a/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart @@ -3,10 +3,11 @@ library; import 'package:flutter/foundation.dart' show TargetPlatform, Uint8List, defaultTargetPlatform, kIsWeb; -import 'quill_native_bridge.dart'; +import 'src/platform_feature.dart'; import 'src/quill_native_bridge_platform_interface.dart'; export 'src/platform_feature.dart'; +export 'src/quill_native_bridge_platform_interface.dart'; /// An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) /// package to access platform-specific APIs. diff --git a/quill_native_bridge/lib/src/platform_feature.dart b/quill_native_bridge/quill_native_bridge/lib/src/platform_feature.dart similarity index 100% rename from quill_native_bridge/lib/src/platform_feature.dart rename to quill_native_bridge/quill_native_bridge/lib/src/platform_feature.dart diff --git a/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart similarity index 99% rename from quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart rename to quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart index 553bf703d..be8f1e907 100644 --- a/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart' show MethodChannel, PlatformException; -import '../quill_native_bridge.dart'; +import 'platform_feature.dart'; import 'quill_native_bridge_platform_interface.dart'; class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { diff --git a/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart b/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart similarity index 97% rename from quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart rename to quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart index 0e46e66fb..44fc57782 100644 --- a/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart @@ -3,6 +3,7 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'quill_native_bridge_method_channel.dart'; +/// **Experimental** as breaking changes can occur abstract class QuillNativeBridgePlatform extends PlatformInterface { /// Constructs a QuillNativeBridgePlatform. QuillNativeBridgePlatform() : super(token: _token); diff --git a/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift similarity index 100% rename from quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift rename to quill_native_bridge/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift diff --git a/quill_native_bridge/macos/quill_native_bridge.podspec b/quill_native_bridge/quill_native_bridge/macos/quill_native_bridge.podspec similarity index 100% rename from quill_native_bridge/macos/quill_native_bridge.podspec rename to quill_native_bridge/quill_native_bridge/macos/quill_native_bridge.podspec diff --git a/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml similarity index 77% rename from quill_native_bridge/pubspec.yaml rename to quill_native_bridge/quill_native_bridge/pubspec.yaml index 4af624b18..c2cf8bc39 100644 --- a/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec.yaml @@ -1,15 +1,16 @@ name: quill_native_bridge description: "An internal plugin for flutter_quill package to access platform-specific APIs" -version: 10.7.3 -homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/ -repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/ +version: 10.7.4 +homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge +repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ -documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/ +documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge platforms: android: ios: macos: + web: environment: sdk: ^3.5.1 @@ -19,11 +20,8 @@ dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.8 - - # For web only - flutter_web_plugins: - sdk: flutter - web: ^1.0.0 + quill_native_bridge_web: + path: ../quill_native_bridge_web dev_dependencies: flutter_test: @@ -41,5 +39,4 @@ flutter: macos: pluginClass: QuillNativeBridgePlugin web: - pluginClass: QuillNativeBridgeWeb - fileName: src/web/quill_native_bridge_web.dart \ No newline at end of file + default_package: quill_native_bridge_web \ No newline at end of file diff --git a/quill_native_bridge/test/quill_native_bridge_method_channel_test.dart b/quill_native_bridge/quill_native_bridge/test/quill_native_bridge_method_channel_test.dart similarity index 100% rename from quill_native_bridge/test/quill_native_bridge_method_channel_test.dart rename to quill_native_bridge/quill_native_bridge/test/quill_native_bridge_method_channel_test.dart diff --git a/quill_native_bridge/test/quill_native_bridge_test.dart b/quill_native_bridge/quill_native_bridge/test/quill_native_bridge_test.dart similarity index 96% rename from quill_native_bridge/test/quill_native_bridge_test.dart rename to quill_native_bridge/quill_native_bridge/test/quill_native_bridge_test.dart index 707f61f3c..84cfe9f0d 100644 --- a/quill_native_bridge/test/quill_native_bridge_test.dart +++ b/quill_native_bridge/quill_native_bridge/test/quill_native_bridge_test.dart @@ -3,7 +3,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:quill_native_bridge/quill_native_bridge.dart'; import 'package:quill_native_bridge/src/quill_native_bridge_method_channel.dart'; -import 'package:quill_native_bridge/src/quill_native_bridge_platform_interface.dart'; class MockQuillNativeBridgePlatform with MockPlatformInterfaceMixin diff --git a/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md new file mode 100644 index 000000000..8eb422933 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +All notable changes to this project will be documented in this file. + + +## 0.0.1-alpha.0 + +- Initial experimental release \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_web/README.md b/quill_native_bridge/quill_native_bridge_web/README.md new file mode 100644 index 000000000..27d23df57 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_web/README.md @@ -0,0 +1,13 @@ +# đŸĒļ Quill Native Bridge + +The web implementation of [`quill_native_bridge`](../quill_native_bridge/). + +## ⚙ī¸ Usage + +This package is endorsed, which means you can simply use `quill_native_bridge` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. + +However, if you import this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. + +## 📉 Note on breaking changes + +The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart similarity index 91% rename from quill_native_bridge/lib/src/web/quill_native_bridge_web.dart rename to quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart index b3898ea4e..8f1a42d0c 100644 --- a/quill_native_bridge/lib/src/web/quill_native_bridge_web.dart +++ b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart @@ -1,8 +1,3 @@ -// In order to *not* need this ignore, consider extracting the "web" version -// of your plugin as a separate package, instead of inlining it in the same -// package as the core of your plugin. -// ignore: avoid_web_libraries_in_flutter - // This file is referenced by pubspec.yaml. If you plan on moving this file // Make sure to update pubspec.yaml to the new location. @@ -10,10 +5,10 @@ import 'dart:js_interop'; import 'package:flutter/foundation.dart' show Uint8List, debugPrint; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:quill_native_bridge/quill_native_bridge.dart'; import 'package:web/web.dart'; -import '../quill_native_bridge_platform_interface.dart'; -import 'clipboard_api_support_unsafe.dart'; +import 'src/clipboard_api_support_unsafe.dart'; /// A web implementation of the [QuillNativeBridgePlatform]. /// diff --git a/quill_native_bridge/lib/src/web/clipboard_api_support_unsafe.dart b/quill_native_bridge/quill_native_bridge_web/lib/src/clipboard_api_support_unsafe.dart similarity index 100% rename from quill_native_bridge/lib/src/web/clipboard_api_support_unsafe.dart rename to quill_native_bridge/quill_native_bridge_web/lib/src/clipboard_api_support_unsafe.dart diff --git a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml new file mode 100644 index 000000000..ff1c01732 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml @@ -0,0 +1,33 @@ +name: quill_native_bridge_web +description: "An internal plugin for flutter_quill package to access platform-specific APIs" +version: 0.0.1-alpha.0 +homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web +repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web +issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ +documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web + +environment: + sdk: ^3.5.1 + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + web: ^1.0.0 + quill_native_bridge: + path: ../quill_native_bridge + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + +flutter: + plugin: + implements: quill_native_bridge + platforms: + web: + pluginClass: QuillNativeBridgeWeb + fileName: quill_native_bridge_web.dart From 0c36eee4ae7930816ea3b6f51eb25dd01570ccbe Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 21 Sep 2024 21:04:10 +0300 Subject: [PATCH 22/90] chore: update description of quill_native_bridge_web --- quill_native_bridge/quill_native_bridge_web/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml index ff1c01732..9b963bae5 100644 --- a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml @@ -1,5 +1,5 @@ name: quill_native_bridge_web -description: "An internal plugin for flutter_quill package to access platform-specific APIs" +description: "Web platform implementation of quill_native_bridge" version: 0.0.1-alpha.0 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web From 82642da1713eb29ae43824727dbc4a2387aef2c1 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 21 Sep 2024 21:11:01 +0300 Subject: [PATCH 23/90] chore(web): avoid using jsify() when possible --- .../lib/quill_native_bridge_web.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart index 8f1a42d0c..a2cfc30d6 100644 --- a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart +++ b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart @@ -38,7 +38,7 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { ); } final blob = Blob( - [imageBytes.toJS].jsify() as JSArray, + [imageBytes.toJS].toJS, BlobPropertyBag(type: 'image/png'), ); @@ -46,9 +46,7 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { {'image/png': blob}.jsify() as JSObject, ); - await window.navigator.clipboard - .write([clipboardItem].jsify() as ClipboardItems) - .toDart; + await window.navigator.clipboard.write([clipboardItem].toJS).toDart; } @override From eb9f10084f291f67945bd147fbae74af6e4c373c Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 21 Sep 2024 21:30:44 +0300 Subject: [PATCH 24/90] chore: publish and use quill_native_bridge_web in quill_native_bridge, update to the newly published quill_native_bridge in flutter_quill --- pubspec.yaml | 2 +- .../quill_native_bridge/CHANGELOG.md | 3 ++- .../quill_native_bridge/pubspec.yaml | 3 +-- .../pubspec_overrides.yaml | 4 ++++ .../quill_native_bridge_web/CHANGELOG.md | 4 ++-- .../quill_native_bridge_web/LICENSE | 21 +++++++++++++++++++ .../quill_native_bridge_web/pubspec.yaml | 5 ++--- .../pubspec_overrides.yaml | 4 ++++ 8 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml create mode 100644 quill_native_bridge/quill_native_bridge_web/LICENSE create mode 100644 quill_native_bridge/quill_native_bridge_web/pubspec_overrides.yaml diff --git a/pubspec.yaml b/pubspec.yaml index f297d8f74..0df80828a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: # Plugins url_launcher: ^6.2.4 flutter_keyboard_visibility: ^6.0.0 - quill_native_bridge: ^10.5.14 + quill_native_bridge: ^10.7.4 dev_dependencies: flutter_lints: ^4.0.0 diff --git a/quill_native_bridge/quill_native_bridge/CHANGELOG.md b/quill_native_bridge/quill_native_bridge/CHANGELOG.md index b90c7c674..6b79d21d4 100644 --- a/quill_native_bridge/quill_native_bridge/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge/CHANGELOG.md @@ -4,4 +4,5 @@ All notable changes to this project will be documented in this file. ## 10.7.4 -- +- Seperate the version of `quill_native_bridge` from `flutter_quill` and related packages. +- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml index c2cf8bc39..bdcb04bd0 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec.yaml @@ -20,8 +20,7 @@ dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.8 - quill_native_bridge_web: - path: ../quill_native_bridge_web + quill_native_bridge_web: ^0.0.1-dev.0 dev_dependencies: flutter_test: diff --git a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml new file mode 100644 index 000000000..2a01ffe68 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing +dependency_overrides: + quill_native_bridge_web: + path: ../quill_native_bridge_web \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md index 8eb422933..167a42b26 100644 --- a/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md @@ -3,6 +3,6 @@ All notable changes to this project will be documented in this file. -## 0.0.1-alpha.0 +## 0.0.1-dev.0 -- Initial experimental release \ No newline at end of file +- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_web/LICENSE b/quill_native_bridge/quill_native_bridge_web/LICENSE new file mode 100644 index 000000000..e7ff73e1b --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_web/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_web/pubspec.yaml b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml index 9b963bae5..a08721c47 100644 --- a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge_web description: "Web platform implementation of quill_native_bridge" -version: 0.0.1-alpha.0 +version: 0.0.1-dev.0 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -16,8 +16,7 @@ dependencies: flutter_web_plugins: sdk: flutter web: ^1.0.0 - quill_native_bridge: - path: ../quill_native_bridge + quill_native_bridge: ^10.7.4 dev_dependencies: flutter_test: diff --git a/quill_native_bridge/quill_native_bridge_web/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_web/pubspec_overrides.yaml new file mode 100644 index 000000000..477638a80 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_web/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing +dependency_overrides: + quill_native_bridge: + path: ../quill_native_bridge \ No newline at end of file From 6fa9c05177f3e024e1af06075a4b27abd0fc0ef5 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 21 Sep 2024 21:36:41 +0300 Subject: [PATCH 25/90] docs(readme): use pub.dev link of quill_native_bridge in quill_native_bridge_web --- quill_native_bridge/quill_native_bridge_web/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quill_native_bridge/quill_native_bridge_web/README.md b/quill_native_bridge/quill_native_bridge_web/README.md index 27d23df57..0ad1b56de 100644 --- a/quill_native_bridge/quill_native_bridge_web/README.md +++ b/quill_native_bridge/quill_native_bridge_web/README.md @@ -1,6 +1,6 @@ # đŸĒļ Quill Native Bridge -The web implementation of [`quill_native_bridge`](../quill_native_bridge/). +The web implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). ## ⚙ī¸ Usage From 12c9b92f7ef301fa0a2f1775cc2086cc0635fb93 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 21 Sep 2024 21:58:10 +0300 Subject: [PATCH 26/90] chore: use .toJS in clipboardItem map (minor change) --- .../quill_native_bridge_web/lib/quill_native_bridge_web.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart index a2cfc30d6..eaac4c245 100644 --- a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart +++ b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart @@ -43,7 +43,7 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { ); final clipboardItem = ClipboardItem( - {'image/png': blob}.jsify() as JSObject, + {'image/png'.toJS: blob}.jsify() as JSObject, ); await window.navigator.clipboard.write([clipboardItem].toJS).toDart; From e9d6285a87ca21aac51fd57162ef1fea7a4c581e Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 22 Sep 2024 01:02:42 +0300 Subject: [PATCH 27/90] fix: use hosted quill_native_bridge in dependencies and override with local path in dependency_overrides --- quill_native_bridge/quill_native_bridge/example/pubspec.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml index 95a368aa0..300565ce5 100644 --- a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml @@ -9,6 +9,9 @@ dependencies: flutter: sdk: flutter + quill_native_bridge: ^10.7.4 + +dependency_overrides: quill_native_bridge: path: ../ From 7479abc8e952ed041c5115c22c852ce7b81217b0 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 22 Sep 2024 18:05:12 +0300 Subject: [PATCH 28/90] fix: update minimum required version of Flutter/Dart by quill_native_bridge --- quill_native_bridge/quill_native_bridge/pubspec.yaml | 4 ++-- quill_native_bridge/quill_native_bridge_web/pubspec.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml index bdcb04bd0..e18aca99b 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec.yaml @@ -13,8 +13,8 @@ platforms: web: environment: - sdk: ^3.5.1 - flutter: '>=3.3.0' + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.0.0' dependencies: flutter: diff --git a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml index a08721c47..737e90dbd 100644 --- a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml @@ -7,8 +7,8 @@ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web environment: - sdk: ^3.5.1 - flutter: '>=3.3.0' + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.0.0' dependencies: flutter: From 2beb39e3c01ec9091539fc6480822c4f2881b03f Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 22 Sep 2024 19:07:04 +0300 Subject: [PATCH 29/90] test: write basic integration tests for getClipboardImage() and copyImageToClipboard() --- .../assets/quilljs-rich-text-editor.png | Bin 0 -> 55581 bytes .../integration_test/integration_test.dart | 62 ++++++++++++++++++ .../example/lib/assets.dart | 2 + .../quill_native_bridge/example/lib/main.dart | 8 +-- .../quill_native_bridge/example/pubspec.yaml | 2 + 5 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 quill_native_bridge/quill_native_bridge/example/assets/quilljs-rich-text-editor.png create mode 100644 quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart create mode 100644 quill_native_bridge/quill_native_bridge/example/lib/assets.dart diff --git a/quill_native_bridge/quill_native_bridge/example/assets/quilljs-rich-text-editor.png b/quill_native_bridge/quill_native_bridge/example/assets/quilljs-rich-text-editor.png new file mode 100644 index 0000000000000000000000000000000000000000..e0a434ff421baf6c5d636a708b7972c68be2a4ff GIT binary patch literal 55581 zcmeFZbySqy+cu1Wg@6SL2pE9UN=hq83rIH@NS8E7i6SZ@NJ@uv_s}6C-OVs`4=D^C z?|F&8_xt{M*R!5yt?#dQeY2L_+%t3SYhQbxah%6_Y(71e6(>ALehv>0k5E$Lu>v06 zNemv|F@rOw;2nQHAtCtBSxX6ZYdk!H%gDcDcwrIO@bGTnNj`q0^B05E^_-e6xVnFoxcK&#h$o|{{39->WBA7| zCQp#+HC&*t_j~>}xJja6+1H=LNGxDIJaGc&%%9(|x!bZCaei5zprNaXNpqf7Uo&LlHA^6oh%k}G$B7_G5@HnBDvu+Q=L=T z_Llp4x0Q}2eec6=1g-U5)u$pw&SWL8-kY5?JGvha>+OIXB2{L?G0lo|m^0|3cdf$L zBNAggEgX4m2sT#aRoLiMj9MTX%_jog~vZ1BP4NxD@l>iBQje<$m{S7%E) z&U==fDk{dy{6Hzf@ePHA2mg$F%g8A4--~WotX_%dF>B#a>c%ZbiWH@vcra3f zDtn3}?QU=Bo99{<;n=L(BJD56U{?$O-RAAfgE7JC#xtnv?BCKEhnRFuS{^iGf_Kw~ zJua1Ob02D`JPP9uIB?{+cKEUtx@S|LlIs+CA`1G@ml*!TU-Fj{W^@|kTBwG5)F z)l0PrxbE?w&-=Dsfu%>%N=wMi_+pD9qp$DHtIwc^l=B?PrNVIDz2U{D8f8J>9WZPKyriiQyWfb{?W5k#*XNI)Q>{W|{%kf&n_m{2(mRuY@ zvLXBP=lq9Gn#kQ|9W# z1hypUuPzqyoTn(zq+HM30Avr`tw2r$kBLwi}~O=T_0%9_3Ch1SriTj!PrPyZdLUNa)KKmDq< z9z7lUaN*s9&6^Qs$tjrQgmlDX;aIb|8Jlv2R8)=3yUS(xG&XB=mTOv=X|w)mGv)AF z+xls)XKPCP(+SO+Hz|^i_IAZn@%JR1tra(INLskYbro*g^W$2cmK*4Zv=Jt5^AOjT zwFrw~3dR8a3gTgva{N0E?EE%sf|hF*cT^;vjb&>eWsz*@<%RLkABiy@?h6p+KQ}Oy{4$S*hjJ7ZsOUDF?Zd= zR?-RxDQv7oqx@yvYg!h?^bYJyHf!9LO~#sbKMR@ChP2+B8ni|A9O%0B#EN*W?|vJ> z)#V7;%y&yh^1M-*2?z-C_m67|VQn(ydQ*~iWIT>yYHHujOXlcESaq}#q(XX`Up1vV zZ=Xw5D)vuRP0RfF@k~#qqWG((VB0ej$dWw|E6r}9ch2)P%lz@a5bPzxF1Ja}%#&6ThyOu&~FaLW@2*p~Lt{KD*ieoR?I~M_c^xk#e!sD~xtl z6gih+@^6{2Mgx%@4fHHXfl< z6qEAJ7d2e+4k*k9i{$C)>529kRMNj2i*+j$%yTLgTahRyKb4c}k_mpq zu1i$mw)L0gy>pm7;-Ru@vh#|z;n)l2Q;k7k+?yh11G%+!2e6ANxEkNtx2LIdX)})Q za!<_IIJMU;PX*h%Cw>-<8Z6|t7}mn*F2vr6auD`sy&1ln$}OjC9?FRIASv6vUP4A$ zv`vxD6UDm!r!zsCbFF+c|D|{wP0Hg2f8#zuDPNRXTeke)vE8r#*A9SWQ`c^UQbLs?X6M2(g?~k@op6 zyLOTa35`Lxb@n#K)CqPbhod=PpaIj+W!iZX?Y5C~@WAege|Nm(Wy0$m5*Zo-HyUKN zrc*naPkV6BptstR+L_ZBxJOQKZ-xk{pfLTxfi{mX4@bn%N>}*NAXXSzYOCt?waw{G z$kE__CrDX$ln4aFk^=6?N%%)OF-X;XAXe;2?O{_cD$a6hagxY+dRElKTlXh1lP$}-cw%3I6OphG~&sIGUA!pcRcJt!NG~y+4ss1cJAWl zF`E9%LFUCg4r>fHO=XSA*nRa3g?Zi4V8;h@2m8C6R=+I4Fy;N%>wlPa=gD!T`5lGI z$%}VnulFr%CSrwjGlFR__9HG%FZaSzwMz@@#u5+kEh;jN!yNll-JH*0Jgye0yRm%q z?W;V%g~99?l#-JVPMpHNbrTF!-aN`LegqkZP8o^ieCl`PqMkyqp_6sij4ZYJrbk@_ zahAYgb{dvd30ao63cd75tC$h>C5Iwxyomn_J zl@Ga%zjImasrau~ds*XtU6y-*Z>Fw_!ngl!^fArn?b)I0t)urXcWuUmHAgp;yplML z{%{;WLbC41Z$Ov`pp)xTq>KutDes;Tbt$oY?lh?P8pYy{6%~y1u4L1-vlYlo-zc5V z?PPvaBgO7%L8^IjX5&!~@z4%#F-_E4FodRjWn6l`C#iCI6RA1i8T6=`*jbZEZ;3qJ1U=wTs$Gi*O4V->M_n z9)pL4A)Pjs?nsjJJ3JMu4Ih#*(%j8rd+e9;@)Ho z(O@F=;u(UAl9mK?Ru`j=rgK9#Y;){-OBC6i2J^4n=<#z=)7I>EuG};84~Xjqz0BWU zQ!%DaFWxOi_O8N(Px3tdQP6j1-;GUd3TAH0Pfgj4t_GxJbrav>LGZ-DVLs8_maWRg zB=0I_`niuzxOsYdmyVPFRu)gMH%-nBZdC4sQ&Q->m^7aGrHWx9NxR{D1W}XCfJ9+? zO9%oIq}Z431%i85t7pObWTW^~+=lEcb-c+!Gx&~Tz55=v;QO~7h{@Q=h5*DyX?DMT zCmRSTO=)G@Dh*57v@Ug2p9{wQvRaxkv))(q|3E|*w}?ii>9=U?ROVbR!~WZobLoc^ ze2);5+*m|&+SFe_+qJ0&CgHKTl+Iu)2y!fjUImbD_Lb|*b=PKh;;*j$b*;blOGUNx zvhT>Pp0>aD@-ms6&GC=lUK8r{*m7X~5L)u$^*8I{%B3Z{rSA#~ip)_W+;ZM``ne~! zllCJ=D%~nIFmBP{?=L?1D(oil>S6u?{Lv4uq)D+E+;t30Oiw5(Fv)`jCVVV0qdmrn zUkPsO=_pn28z-o`wlx;8xUO(BziOi+Wt) z5T{nwePnB2EHU&hOur>~S`hA=7Q)LKHEQmhzAvj86dte6ZeTp|+b8vDY}_frPpcV@ zM@BOG;gQG-rC4E6B+TXMw>E$5)H=^@iz0e7ElTp`(s)mV>7R&&SB?EcnJQrrwI_Zj z_!ch;J-fG7bhI>87Gq;$^Fq(EX^e)7a-^hq$Dea{H7k}ovj6GYT546AK&TEzFAs!Vm+-Iz?ZUP7b9U7RYCNo6RKMY~STcdgZ$ZbC7C zgF8iSm8*PR%0iNiEHF)!Bq9wnM0gQbIN0KHW2Q^^+>#0}PHl?LXs$D1u)?Jj$pDC_ zCP+d&(=r*U5SSmBz~?k_%Bs-V%&5>n$;*Tdr$q_qtnhWS?L_QuEOtn<+Vr*w?cdEC zV9Favk9OOUXDx3J9fhDV$%uJV7I%(>!5F@$QDT#_7I%*2s(f|`?sGhg#nd#HY^>(= zvz8u0?M$Wl*haJIH_MX#y!O~7>1fzNtAgU?HkHF;??`%9$w(Mgp45IOQBAqmpO+}}376&z%Um49`6gFiUR|a1l}1Om&h*X1ic?lPN+re7 z60M5j>4&<-Rta_s5=wv@dSWN?DsV)wN>|Ypi*E!5r_UxW`W8A0qfurpBr`+Bw>z?2 zU`R)K005~9Q;mZ;x8)gCGxJ#)n~Emy^RKa-ZgQqjomqEsNa?zua8*P~Te-9)d^eV6 zZq6WTvydLvkpJi8^XTX%E&Ry{7zy8t^IfjKf`1YNH#RZ-L)1>I=k?WT8bb(g4Dz}0alC>7m( zzDNZ!r7&w2-o1IVdMd)4jESnUciojlamJeOz4?*~vn|TqV`4EPZHUzM&nKD`?cKYc zo}P}gKruZWUK?)`;9SHs?7ZkOD6yIIXVET+XC-w>G+LJJ^=92{CYf0fvi#Vz3E1-a zJp88XauUd(mY+X=c4xW-#(y!DvP!pWGX9ui{Yq=vTCc_CJ}vD8hphMA2VUl+($kXD z=6m_^cizqIGE2$GC@-Q-8o!BGg8*AHvp0+Q~TF0}^9yyntBBqK5ylXUg{!nH42sz>H zbyQl&(VC8=HUx~w&#-8lzc*KKdJr7-HFW5`#n)qDrA?tcOHCX};icp}Gm9*$j0NaI zs1GC+uwK-!`+&JaUN>%}hX!)~@YiBa9yPB|-XsgFxM|7E8VH-!wZVOLFP-Z1mv}T zAH()$2GQ(KRP3G|I=Y|V1Uxzpph!sIH|2!2>sH&jYQl+{yjs>|C61-nQ%jYlE7cEu zXi}hrI9LZF4|&ydnQ*;oNu?Q4xo=zHW3W-@sb{((_}kjBe!T2u*KZ&Hc)FG)+zw*0 z@N)l_J`l*6W_6}u0iZ7?SXnKkwPi(NW}dUb>SbQ->h~uoDG7lUS@J z)cw2iIfF`>4CFWk`0&!DOA%H~b<&}EtbGKm4ozu1m~=viYlg<9f!QvlG)Q$NX#bB) zyywsPBUfK#PAf^PaI0n65ySRg&%7RWKed(Qwb1@v0L@%x>HMoo`=i@D;5;T*mSZW&OEED=4A31eYdC!7OF;; z#R6SJ)=2H0^RiAUUG2Vdmd@?naxiq{l^g4XBS-4fuata=c?NBbw$&QFK_;?qa?i|Z zDU~?pee|gLR*2no+gJ8iVvh@+Q90UC_vNdA+?QT8e6~EzVUth=oyFPn8rHyBaV#7yLhJ7facx@H@l`@Ai2Ij9E zedXDezxUcBtadI||Ckj}^(uedd9`^U&p#e2?bYS~Y*rB@Ge;PrZ+-9A*; zdUS8Rx6hH~2oeyn;<~E!!JIUN(=JoP`ua)(@KKBS8V>15a}rv!lt>vmW=eyRlC*a(&ftqdJUI?7?hNb+QL!xsiHyKb)m_VA za|xr7r|+YnsMwRP&W>?A+&@Rqv`a00nwTmbF5IyRFI}!cuWTy5*<9)gq*n}tKDp~yD=+yp*`9mEWV^1AQH|3f{o2U3mZF6@D z;tp>qN0MgjRL_QAy_8g*8gG!8YIbbDd0}2g8H!iq?&R33Iu4I~D7c9vN~4C8uWg^A zP2>JjQ4zcTyVm<^f%CQ%kRhUXWF!1&dXnWuWWqQ)CVYi5$7{VQhV|`Sb{A2D)xt( zlFM5sQ!Dh#8u*xKlH^Qg z+hZBjbK`}Dh4WqaceF@Kj^SSf4rRxvM59ix?3nigMOMlp3Z}PqMOsmmk{Wt%3?qLU!SB-)9mnOZYjs7%} zhD~m+saT#|cU*aw#w`KN%W0zP?H@1R<}m7{sTp}NZ6N;jdWYXlk-&(Eg>09;Ol>GC zLV7c`)B5}SV}zDNnKkn3?Y7t2gt$3utVt3n1)bFtlVl5&d#4hrWc**ec%hUeOPQfv z+Doc3Z4f0Wfq$#W&R*8LHV%+4*>IHdG;zTOwVJ>pyGD|1B#k7&RhIMh+b+s^`Yq4w zj}?C@t;)E;=YvA)61?Sl>{wb-|0;b3WJZ3&U!0uaC%P3uoJ6dmZ=CrkK7G zTdXN6XaK%sJJm>HJJ%`dE4Y(9Hq*wkdtoa?*R><&(L1FCDUy~*KJJg@$B%orGkZm7 zZKReo`^8;Nm(C*MEfCJKTZ&Vi(p0mv8b~8A>0~+27o?{~jjkw=?I2{&yd!Ip5xbv8 z@2a6m@=I+N?nM5;GUh9_5Q`-8OX8Ez7el8HRWLnz6DKnaRV*2Qmt9<$xk<4w@&w^g zMh*{9!OWV8#6o*dk@=SiU@3xR!nvT*d2QSt^OjCF!q94dx73tfpair|ou3|K3X0<`4~% zzJJeb-=1vN`1bDF%;Q&zvZ``Zg~rTodPGCH=k&{hLa4&qgbuYxy+!4O_Vc6M4jd)t zf}G&d5Lcj+i%KVB*S|KLiSo^nzxkbe=IrB9eXj5V$2EP(lJ)JZ$4|Uk94zpUucLay zR4;JsxVPsOt6!3|=_9uVIs|TO(T}KLqWw0~<=Q26TLbgOoyMujdJLw}#`7h%1Ibp_ zS*~k^ox>J^mW?ZlGiOD-hzjrp{B8EOUK3Ia4i%j`*QekWd)Ut1)r*aIZO~$W{DA)& z4bVBPWh)*u^z;oy_lAP@cQ>Vh1~7^ZCz{7}wp! zb7vd0-7Cd(Rp_Xb?li-@xG+ih_V z3`sNuU|E-)^&G;apw2Yf_;o2>@RCRGPNm323-a2tIJjx|WoRa4RqQaxDaGz!!BCN@ zKKyy&4XJl+uk5{OMGH}J@zeXb5KZ+YU&F3MpZN7EW?o(mBqcz^-B=#U`XTOnDt-~d z(7=c1r^C1pmwXN|_v{wb%J=>>l~_%EK6jNR{q2d<^Ua~`9}1txr>SPmqb(|f8uZR> zEnm3NZ`GId-8s!8wbyD`qw52?7E|HAEa&CO3;i6;98wd8A7#4YHc9Okhtr}C~!@tsAT zs2E%wZNfrF*>|e>jtuV-8&~DEOO$O1U1!4t*2Oe2P9{J5DjwJTZu~Y=jR!4-PB3>` z$(~eY0z*v*cYZX;wjDF8yL_7wV_uF^k?aKLu%M-*n};tJx_F)=>3p+Xp49O}oT699 zE>h4%Bb?hJm_}4oGzARFKfWcL8@oQ&^&$To&l789b$dWWUN1eSe7J|7u6`N3q7P-o zUoF6RZGlN2OUw8BB8rJmIx1XtmC77mLoREz8~T3#rOm;fqf8XPI;{LgX*t~NO^=RF zt{IxkcA@{irmr^&A+$2JOMzwAahgjA;c!|v3~iZqN12>7nOcJl2=w<~wQnLXQLh>D zzREgZ_4WkBqG##etUc6Ub(seb895!nwr zCeau*2GSRrfv>04EXPs_I3fPe^M}8#t`0q1N=C01WZt`p52=rRZ%7PX=9tH#Taiu& z{UfCmh3X}u3s29pw%o*pqk>+1Fm|%8yBQHWFl|zL^W`visbk*;kuo-@cPwTO{Yr?V z+i0oCuqW(3X7xK$WcO!Z6$$&tX|ElzJPETEr#W4IQ%QdNtaGo&JbA%pF$crt){Gyb z$jGj;cqJnhqmM03tm*#QqqS;ur(oP?pk&bAUoiazCOE!U#L2`7MNFbyx#Hu4Eijz8 zCliK?{fs6x*pgjS;qHj1=Pr!Y%{uE+XJ{SCX3YIZ#Fo`{VoyZOP*fCquaAb=b)Nzw zcwa|AT2kTjr5ECcuOn0DpNjIZaA*0=EfFfNO}CsF%(VSEx%YI=4mJ#j?UPHAi|&Pu zwb@#ME>fu~G{j0Q$2?fHielDg+R(0r79%>^B{pY`qw9RBS-oo^EY!RWs(sP1wK_o< zo<1!2Brhu+YRjGZw5)spWY7+~Ioo5doT}6jZc&i}&OAW(m6(8_>;2iwkhOlZ`m_n| zGKr&`m~(fY0c7FwA?K6=Z}z*#m$t!WK9md@B~7EinAdf$sN(S8O@>;Iu$%xcP35&RNO1SKYR0q9^=8 zGO`&{@+-fPS#8vch|_C(aAC$JEo;_xHMO_WJFzb;-gY1JScCgG>Wg)B_vZs`4s8WA zQ%TX!Obc$OWJ&A-SiR(M#>qS$C+#j1w=%a11G`Mu zfjAlC(p6B_YLIGSb~oRH$}!jD16_n7`c9*SDgk4zs(nwc%bb#yk7iG2-KUxESgDm6 z`6dFvtq8Y>LtXvD2sw>%EaW`h`2 zO>5q04*->%sZ|_zOZ2l|8s#n?v-PV;8W&uT>LB;86-T9M{YUDEAlXmMifvbTV zCt7-}zbSg)B6eyjvf!g!&su!j2im}PBE#%sbq_nC&O}hZ*FN&GOKk4z2{GuvVWHm} zd~=zHWgwu;59Zam25nSe;XSESFFq=>SZyq*gN>#-td0Zyp`KlO z;^g&MO`_Cx*VNvE5YtC$PfRi##>L$2j6J6_z9_AG7=np?!0n4^>oY05cw=!qeSJQ^ zzGBtxxek5-<;4sOUIT%YH`5o|)#KyD9}s!T?3tEq@ZWDBEs|8&KM3fyn|j)!*?a$n zb<5Nh)uu0}O{WU@O44R_R5|b5RCq_ux5QgC6F*;QchN4WSp2Z4Na8fE%BN54If)&^ zEz4n*L@i|!V%7x*C5P8@Lp>C#49UhhSy#Aia=yM-ksw}Zr3$2)o881TeZB5Nvn#dX74#J>`&g_bGLT4hvL65q=Vi9TZlHdPX5?L4*?GVFR$Ig z_Y99aLd@}g+r^fBz7Om7BBrNBPg>36Lc*y}r~<&t;IW>bT||xa0u(cK8%b44i31FT z7|~~MZ$+Rw^6eHin03n3Sj*QH3ek~5ZWU0<<+)Hc)7x#laLabRjvXaUd8fYfF>>y1me)K);1torzRnrRu~v0%_md={+UA zwSHn>uhY{nmscE9wHo;)fiH|-5LqaT9X4jnh)5zyqM_uA6G%-+4Hdkslsw+VO>)Op zFH0#+DlsO_S($9jMSS`oq3!-vbx!MSl}A0t*LWDkY46`K{)ud{`UBu-?H> zd%*fo#sTX#Vzkq&Wb1$xk#R98`|V4ewaSV*qhW8g;aE?#TYCi<+fDcUZ8fLip?ssQ#d;MY8b z4?I!nYi$CXL)JthQBUXsfK{alXwc)MD~)oRrx(~;sZm$y75Gzs+uuW#=k@xmJTea8 zUMQ-IsND8m16T&s&VS?b3u*x;B}ih{15qw`_n?AD#yPd@PpnM^2~DCFLpmc>v< zLGCmL<>P`T`GOI6R~rP700Ot?MGAHOo6f*S%Zb&m zLApU{^a`gJS=@faD=N5@KEqCSyz}q-rtZ{_Yv~A35Gl2jrELhwb6|CO`%ysh4z1S? zYJEkn*e$`>7m(ZJ3bT1tWe$^uE~Yf2uf6!}ju&QH;~NPF-)ekRS`_^d|E8`Pi@fRw zIo8UVN-5(GdF4fh$xdCb>TeTmB3%8fiBAhn2aYU*!?69Kz?+Tk-Q~(G)UZ?2b6qGQ zoHm!tX4?n+{4P-PSUpCb0vIJ_l({yN*#Uy)Dc29dwtXlFp#(H#zbm%gAx>S8p%jl@mnn91bdaP-Xuf<9sC)9Ue&KfM5DqL`qlU=FOXreEI)KR8R^kgQ)=SXB8i8BaoMttN5v{(X7>?qvDf zUWMz6!vej$f~Ckt#)-(-9{XRRprBZ>VDjd66~%Dl+P{l1 za~%9G{3%n4t-ct5lbkdZP~H9g*wjc;gMaCAj}z^%5mAw8nwE@b-*u(W4Dr#s&K{)9 zf_H82mE2rDIpMexoW%WP6JsC!e6c8uW}rmsWelCj*Uz67AeG>1ujgobHU%+W4OIT)(XcaCRgr5x-5fgKmE;@04t$yI zd_28k;w(r>2UDQq=+8NK_N+V%IP5S%2$s~A?dY%o8kRJL1SuADp6+&`Y$PwQ z_4JqNwrGpp{cjW*I^`PJzO26OwV78rY8(zL&-VA1-Bf{xI^H?_fsleG!AgZ<>>`v>=td=XBE(I)Ss(;TCop zw0*pXS@LOV6xEnVt)BGe`mjszG6OY z{>3K21FU0juKAr@i~PYQ!gei*zZ^(v*l>xrMq5$N;#YvW))BkRS&e%@fs&|B=yVw8 zj8&3_3o|A!)<^Bk@86%lKNp{=6QsniK&cy<67}}lbG3?$51dgku^4?uQBURROd|53 z<%=?cWbTD!fopB=3_?{W^Ue_xDkn$ z(5wsa1#N=ciD96V5h%%^GqmC`a7VMy^fgcSKmfo60~1;_$SO<*a$gmG<~AACuQc>< zd8M6As%TlDVUnVdaGWi$PLFM_d~^1ird}AV2H`gj0(TR25;r@emU0)2*H1AnoCJXp;{NbZfY-Y>v3; z`U-4(k8<6ai(zJ(JWD`7Y2MpdvlqM;!H-K?tO$LQxPI7~r8meT8S18=S>N2hRR4H( z`t0B11bff9!r!SVAIEc-KZA*1cyFGrgEVxmeL?g|7yic$k9+_1$%FrH%SKHto6qm+ z-g=i@_0mxr`QcZi{_N-3rG7IPOB~-`CI8#9sb_?09{K?f&$AvWnefhw|EJmXe?B@GCrps3;pAR-4e!J}F2rO~sd z4&OOl$_Vsh2myf|D8K8?Iy*aURz@FX=vL~4bD4E!RUV`QNhR$MaWVm5crsK}PAk=? zmD5z1J>Q>P=1X6$Jk)_ur;w(Su?~PVO)cjI(eCBTmpP%O2+Dvb@Xw~-9yhOyREz%I z6)tZebpnZy4`mrFhKxlyE*O9!lqr)odq9~L zylNsv?;H1L#{zi2R9||*65s*C%m&wKm)gkyL$9Er5e9p}{~6AJgQ*2pfRf)FL4nXHZt<^@`&CSlvk6M3jj6p-40!u)v_N#E!zj3 zo9DRAcvoO9jFJ0t9s&NV~)2C3P{}DA@?SJVzms3a8^rr~OUumRjlZ_V4063ch z!JntSv}$Lyjv5Doq$l3Hh5>X4L zBSLW4%95#I%^@n?HD1IffVKf|C{z%K zmN_nx#@C&|bNBe_30-#lK_JID^y)7lYYo#0d+<80u8KJis3Vi{U!OjI`qTkzJ16pm z2bMS71zORd^Clr7Nq|<<6QmsqWgP%S4-7K|%_>Vc>E?c%k%vbeK@9+9dcd?0Z~*uR zx6L0L8|$=Mdlh*T>4k#IycZhNC)y|WVhuau5Hf>=lr-`E`;%1Qmw1!Xe@8QcUIaqA zK@FV&ock6@kvg>NQ^kF$pj%S_rjc~GHzRavgX1tor@RDVDB|O3A_d-X_5VH7_cs~c z^#gdD0QX{qwpCWypU+Z(cfT!K2j7(!28&d7a43S7R2*O%iuSI(6w9%n;0oCg4g!ON zNf(xoeDDF0H(Zer+FN<&x;=HZz_8=zvw(1Vq3K1)#|mF{FY|<4^>kDV zY+Vy&9z+8hC53ba7KY%bA{ZlxM+<=dO=8G|ek~*TrA6g_q3X;oHmmXqEG+>3eO#Ze z>@G1BaL@`h9<6%218q@*dYAgM;6^>*#w6tA^HyrhNF;%CFlL};Y_f*Z;0e;p1fxY7 z%fMVEgNY(l0heii40ySIMFV2xGlH8HR1ai zDw)TR--C-^xqSJyBv?_7S>D0SV&%JsrOw;A$aDlA*ew|kZ!mO?>}AVuyG=Ps^aP_rVzxDRQK zj8+|k=B+flezT>cE_hc@g7j4=h-UXUM^vH41@mV_Edr5bGT)s7Lc<<}thsnG7P8X7 z@fgr9%s~`ORY>>@&8Z1XG*k$uM=!2KK@SBOWt`jLrW*k5k2EP-#a0l5qd;Jh2@ZlZ z#z9BW3Elqg0u$9}H1yL4ocIx{y4ZRK=}N!>q;G+%&N>y&>R?a`FJ8pyH-C=>KQe;y zQ%4{ZI45f7mA4R{KqC#Tb#aUkoI9B0{_C_ekUTqKARDw93OSPWA-WQNmMj*cO}$TE zDB(fOISE?bN-zo(`Wq&KY?mdL&ja713sl#d

3V|!*$A? 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 verifyImageCopiedToClipboard(String assetPath) async { + final imageBytes = await loadAssetImage(assetPath); + await QuillNativeBridge.copyImageToClipboard(imageBytes); + final clipboardImageBytes = await QuillNativeBridge.getClipboardImage(); + final pixelMismatchPercentage = + await compareImages(src1: imageBytes, src2: clipboardImageBytes); + expect(pixelMismatchPercentage, 0); + } + + await verifyImageCopiedToClipboard(kFlutterQuillAssetImage); + await verifyImageCopiedToClipboard(kQuillJsRichTextEditor); + await verifyImageCopiedToClipboard(kFlutterQuillAssetImage); + await verifyImageCopiedToClipboard(kQuillJsRichTextEditor); + }); + + test( + 'copying an image should return the image that was recently copied', + () async { + final imageBytes = await loadAssetImage(kFlutterQuillAssetImage); + final imageBytes2 = await loadAssetImage(kQuillJsRichTextEditor); + + await QuillNativeBridge.copyImageToClipboard(imageBytes); + await QuillNativeBridge.copyImageToClipboard(imageBytes2); + + final clipboardImageBytes = await QuillNativeBridge.getClipboardImage(); + final pixelMismatchPercentage = + await compareImages(src1: imageBytes, src2: clipboardImageBytes); + expect(pixelMismatchPercentage, isNot(0)); + + final pixelMismatchPercentage2 = + await compareImages(src1: imageBytes2, src2: clipboardImageBytes); + expect(pixelMismatchPercentage2, 0); + }, + ); + }); + + group('getClipboardHTML and copyHTMLToClipbaord', () { + // TODO: copyHTMLToClipbaord() is missing. + // const htmlToCopy = + // '

Test Document

This is a sample paragraph with a link and some red text.

  • Item 1
  • Item 2
  • Item 3
Footer content here
'; + // QuillNativeBridge.copyHTMLToClipboard(htmlToCopy); + // final clipboardHTML = QuillNativeBridge.getClipboardHTML(); + // expect(htmlToCopy, clipboardHTML); + }); +} + +Future loadAssetImage(String assetPath) async { + return (await rootBundle.load(assetPath)).buffer.asUint8List(); +} diff --git a/quill_native_bridge/quill_native_bridge/example/lib/assets.dart b/quill_native_bridge/quill_native_bridge/example/lib/assets.dart new file mode 100644 index 000000000..d5c65c88b --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/lib/assets.dart @@ -0,0 +1,2 @@ +const kFlutterQuillAssetImage = 'assets/flutter-quill.png'; +const kQuillJsRichTextEditor = 'assets/quilljs-rich-text-editor.png'; diff --git a/quill_native_bridge/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/quill_native_bridge/example/lib/main.dart index 06f3024c2..195592117 100644 --- a/quill_native_bridge/quill_native_bridge/example/lib/main.dart +++ b/quill_native_bridge/quill_native_bridge/example/lib/main.dart @@ -5,12 +5,12 @@ import 'package:flutter/services.dart' import 'package:quill_native_bridge/quill_native_bridge.dart' show QuillNativeBridge, QuillNativeBridgePlatformFeature; +import 'assets.dart'; + void main() { runApp(const MyApp()); } -const _kFlutterQuillAssetImage = 'assets/flutter-quill.png'; - class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -38,7 +38,7 @@ class Buttons extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( - _kFlutterQuillAssetImage, + kFlutterQuillAssetImage, width: 300, ), const SizedBox(height: 50), @@ -141,7 +141,7 @@ class Buttons extends StatelessWidget { ); return; } - final imageBytes = (await rootBundle.load(_kFlutterQuillAssetImage)) + final imageBytes = (await rootBundle.load(kFlutterQuillAssetImage)) .buffer .asUint8List(); await QuillNativeBridge.copyImageToClipboard(imageBytes); diff --git a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml index 300565ce5..276de35fe 100644 --- a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml @@ -23,6 +23,8 @@ dev_dependencies: flutter_lints: ^4.0.0 + image_compare: ^1.1.2 + flutter: uses-material-design: true assets: From 0dc9a42ced807f995a9b6808e47a464e1c8dfa3f Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 22 Sep 2024 23:52:15 +0300 Subject: [PATCH 30/90] feat: add copyHTMLToClipboard(), add integration tests for copyHTMLToClipboard() and getClipboardHTML(), minor cleanup in the example --- .../QuillNativeBridgePlugin.kt | 27 ++++++++++++ .../integration_test/integration_test.dart | 31 ++++++++----- .../example/lib/assets.dart | 6 +++ .../quill_native_bridge/example/lib/main.dart | 44 ++++++++++++++----- .../quill_native_bridge/example/pubspec.yaml | 2 + .../ios/Classes/QuillNativeBridgePlugin.swift | 8 ++++ .../lib/quill_native_bridge.dart | 10 +++++ .../lib/src/platform_feature.dart | 4 ++ .../quill_native_bridge_method_channel.dart | 16 +++++++ ...uill_native_bridge_platform_interface.dart | 4 ++ .../Classes/QuillNativeBridgePlugin.swift | 9 ++++ .../test/quill_native_bridge_test.dart | 36 +++++++++++---- .../lib/quill_native_bridge_web.dart | 20 +++++++++ 13 files changed, 187 insertions(+), 30 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt index f32497755..a71ac574d 100644 --- a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt +++ b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt @@ -67,6 +67,33 @@ class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { result.success(htmlText) } + "copyHTMLToClipboard" -> { + val html = call.arguments as? String + if (html == null) { + result.error( + "HTML_REQUIRED", + "HTML is required to copy the HTML to the clipboard.", + null + ) + return + } + + try { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newHtmlText("HTML", html, html) + clipboard.setPrimaryClip(clip) + } catch (e: Exception) { + result.error( + "COULD_NOT_COPY_HTML_TO_CLIPBOARD", + "Unknown error while copying the HTML to the clipboard: ${e.message}", + e.toString() + ) + return + } + + result.success(null) + } + "copyImageToClipboard" -> { val imageBytes = call.arguments as? ByteArray if (imageBytes == null) { diff --git a/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart b/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart index 7a8861211..5d983da20 100644 --- a/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart +++ b/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart @@ -1,4 +1,3 @@ -import 'package:flutter/services.dart' show Uint8List, rootBundle; import 'package:flutter_test/flutter_test.dart'; import 'package:image_compare/image_compare.dart'; import 'package:integration_test/integration_test.dart'; @@ -48,15 +47,25 @@ void main() { }); group('getClipboardHTML and copyHTMLToClipbaord', () { - // TODO: copyHTMLToClipbaord() is missing. - // const htmlToCopy = - // '

Test Document

This is a sample paragraph with a link and some red text.

  • Item 1
  • Item 2
  • Item 3
Footer content here
'; - // QuillNativeBridge.copyHTMLToClipboard(htmlToCopy); - // final clipboardHTML = QuillNativeBridge.getClipboardHTML(); - // expect(htmlToCopy, clipboardHTML); - }); -} + test('copying HTML to the clipboard should make it accessible', () async { + const htmlToCopy = + '

Test Document

This is a sample paragraph with a link and some red text.

  • Item 1
  • Item 2
  • Item 3
Footer content here
'; + await QuillNativeBridge.copyHTMLToClipboard(htmlToCopy); + final clipboardHTML = await QuillNativeBridge.getClipboardHTML(); + expect(htmlToCopy, clipboardHTML); + }); + + test('copying HTML should return the HTML that was recently copied', + () async { + const html1 = '
HTML
'; + const html2 = '
HTML Div
'; + + await QuillNativeBridge.copyHTMLToClipboard(html1); + await QuillNativeBridge.copyHTMLToClipboard(html2); -Future loadAssetImage(String assetPath) async { - return (await rootBundle.load(assetPath)).buffer.asUint8List(); + final clipboardHTML = await QuillNativeBridge.getClipboardHTML(); + expect(clipboardHTML, isNot(html1)); + expect(clipboardHTML, html2); + }); + }); } diff --git a/quill_native_bridge/quill_native_bridge/example/lib/assets.dart b/quill_native_bridge/quill_native_bridge/example/lib/assets.dart index d5c65c88b..aea78ea8a 100644 --- a/quill_native_bridge/quill_native_bridge/example/lib/assets.dart +++ b/quill_native_bridge/quill_native_bridge/example/lib/assets.dart @@ -1,2 +1,8 @@ +import 'package:flutter/services.dart' show Uint8List, rootBundle; + const kFlutterQuillAssetImage = 'assets/flutter-quill.png'; const kQuillJsRichTextEditor = 'assets/quilljs-rich-text-editor.png'; + +Future loadAssetImage(String assetPath) async { + return (await rootBundle.load(assetPath)).buffer.asUint8List(); +} diff --git a/quill_native_bridge/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/quill_native_bridge/example/lib/main.dart index 195592117..f5f2d1286 100644 --- a/quill_native_bridge/quill_native_bridge/example/lib/main.dart +++ b/quill_native_bridge/quill_native_bridge/example/lib/main.dart @@ -1,7 +1,6 @@ import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart' - show Clipboard, ClipboardData, rootBundle; +import 'package:flutter/services.dart' show Clipboard, ClipboardData; import 'package:quill_native_bridge/quill_native_bridge.dart' show QuillNativeBridge, QuillNativeBridgePlatformFeature; @@ -58,6 +57,14 @@ class Buttons extends StatelessWidget { label: const Text('Get HTML from Clipboard'), icon: const Icon(Icons.html), ), + ElevatedButton.icon( + onPressed: () => _onButtonClick( + QuillNativeBridgePlatformFeature.copyHTMLToClipboard, + context: context, + ), + label: const Text('Copy HTML to Clipboard'), + icon: const Icon(Icons.copy), + ), ElevatedButton.icon( onPressed: () => _onButtonClick( QuillNativeBridgePlatformFeature.copyImageToClipboard, @@ -92,9 +99,9 @@ class Buttons extends StatelessWidget { }) async { final isFeatureUnsupported = platformFeature.isUnsupported; final isFeatureWebUnsupported = !platformFeature.hasWebSupport && kIsWeb; + final scaffoldMessenger = ScaffoldMessenger.of(context); switch (platformFeature) { case QuillNativeBridgePlatformFeature.isIOSSimulator: - final scaffoldMessenger = ScaffoldMessenger.of(context); if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported @@ -109,7 +116,6 @@ class Buttons extends StatelessWidget { : "You're running the app on real iOS device."); break; case QuillNativeBridgePlatformFeature.getClipboardHTML: - final scaffoldMessenger = ScaffoldMessenger.of(context); if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported @@ -126,13 +132,33 @@ class Buttons extends StatelessWidget { return; } scaffoldMessenger.showText( - 'HTML copied to clipboard: $result', + 'HTML from the clipboard: $result', ); await Clipboard.setData(ClipboardData(text: result)); debugPrint('HTML from the clipboard: $result'); break; + case QuillNativeBridgePlatformFeature.copyHTMLToClipboard: + if (isFeatureUnsupported) { + scaffoldMessenger.showText( + isFeatureWebUnsupported + ? 'Copying HTML to the Clipboard is currently not supported on the web.' + : 'Copying HTML to the Clipboard is not supported on ${defaultTargetPlatform.name}', + ); + return; + } + const html = ''' + Bold text + Italic text + Underlined text + Red text + Highlighted text + '''; + await QuillNativeBridge.copyHTMLToClipboard(html); + scaffoldMessenger.showText( + 'HTML copied to the clipboard: $html', + ); + break; case QuillNativeBridgePlatformFeature.copyImageToClipboard: - final scaffoldMessenger = ScaffoldMessenger.of(context); if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported @@ -141,9 +167,7 @@ class Buttons extends StatelessWidget { ); return; } - final imageBytes = (await rootBundle.load(kFlutterQuillAssetImage)) - .buffer - .asUint8List(); + final imageBytes = await loadAssetImage(kFlutterQuillAssetImage); await QuillNativeBridge.copyImageToClipboard(imageBytes); // Not widely supported but some apps copy the image as a text: @@ -164,7 +188,6 @@ class Buttons extends StatelessWidget { ); break; case QuillNativeBridgePlatformFeature.getClipboardImage: - final scaffoldMessenger = ScaffoldMessenger.of(context); if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported @@ -191,7 +214,6 @@ class Buttons extends StatelessWidget { ); break; case QuillNativeBridgePlatformFeature.getClipboardGif: - final scaffoldMessenger = ScaffoldMessenger.of(context); if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported diff --git a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml index 276de35fe..28aac57f8 100644 --- a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: dependency_overrides: quill_native_bridge: path: ../ + quill_native_bridge_web: + path: ../../quill_native_bridge_web dev_dependencies: integration_test: diff --git a/quill_native_bridge/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift index 53428a10a..671ca7b34 100644 --- a/quill_native_bridge/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift +++ b/quill_native_bridge/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift @@ -24,6 +24,14 @@ public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { } else { result(nil) } + case "copyHTMLToClipboard": + guard let html = call.arguments as? String else { + result(FlutterError(code: "HTML_REQUIRED", message: "HTML is required to copy the HTML to the clipboard.", details: nil)) + return + } + let pasteboard = UIPasteboard.general + pasteboard.setValue(html, forPasteboardType: "public.html") + result(nil) case "copyImageToClipboard": if let data = call.arguments as? FlutterStandardTypedData { if let image = UIImage(data: data.data) { diff --git a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart index 42fffff5b..761bf5849 100644 --- a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart @@ -9,6 +9,10 @@ import 'src/quill_native_bridge_platform_interface.dart'; export 'src/platform_feature.dart'; export 'src/quill_native_bridge_platform_interface.dart'; +// TODO: Support description of methods here is outdated with QuillNativeBridgePlatformFeature +// web support has been added recently and still not updated. +// Remove 'non-web platforms', 'Doesn't support web' and anything related. + /// An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) /// package to access platform-specific APIs. /// @@ -38,6 +42,12 @@ class QuillNativeBridge { static Future getClipboardHTML() => QuillNativeBridgePlatform.instance.getClipboardHTML(); + /// Copy the [html] to the clipboard to be pasted on other apps. + /// + /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. + static Future copyHTMLToClipboard(String html) => + QuillNativeBridgePlatform.instance.copyHTMLToClipboard(html); + /// Copy the [imageBytes] to Clipboard to be pasted on other apps. /// /// Require modifying `AndroidManifest.xml` to work on **Android**. diff --git a/quill_native_bridge/quill_native_bridge/lib/src/platform_feature.dart b/quill_native_bridge/quill_native_bridge/lib/src/platform_feature.dart index 22df887e6..93a5b0cd2 100644 --- a/quill_native_bridge/quill_native_bridge/lib/src/platform_feature.dart +++ b/quill_native_bridge/quill_native_bridge/lib/src/platform_feature.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart' enum QuillNativeBridgePlatformFeature { isIOSSimulator(hasWebSupport: false), getClipboardHTML(hasWebSupport: true), + copyHTMLToClipboard(hasWebSupport: true), copyImageToClipboard(hasWebSupport: true), getClipboardImage(hasWebSupport: true), getClipboardGif(hasWebSupport: false); @@ -47,6 +48,9 @@ enum QuillNativeBridgePlatformFeature { QuillNativeBridgePlatformFeature.getClipboardHTML => kIsWeb || {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} .contains(defaultTargetPlatform), + QuillNativeBridgePlatformFeature.copyHTMLToClipboard => kIsWeb || + {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} + .contains(defaultTargetPlatform), QuillNativeBridgePlatformFeature.copyImageToClipboard => kIsWeb || {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} .contains(defaultTargetPlatform), diff --git a/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart index be8f1e907..5a2cac142 100644 --- a/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart @@ -46,6 +46,22 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { return htmlText; } + @override + Future copyHTMLToClipboard(String html) async { + assert(() { + if (QuillNativeBridgePlatformFeature.copyHTMLToClipboard.isUnsupported) { + throw FlutterError( + 'copyHTMLToClipboard() is currently not supported on $defaultTargetPlatform.', + ); + } + return true; + }()); + await methodChannel.invokeMethod( + 'copyHTMLToClipboard', + html, + ); + } + @override Future copyImageToClipboard(Uint8List imageBytes) async { assert(() { diff --git a/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart b/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart index 44fc57782..10d9a443a 100644 --- a/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart @@ -34,6 +34,10 @@ abstract class QuillNativeBridgePlatform extends PlatformInterface { Future getClipboardHTML() => throw UnimplementedError('getClipboardHTML() has not been implemented.'); + /// Copy the [html] to the clipboard to be pasted on other apps. + Future copyHTMLToClipboard(String html) => throw UnimplementedError( + 'copyHTMLToClipboard() has not been implemented.'); + /// Copy the [imageBytes] to Clipboard to be pasted on other apps. Future copyImageToClipboard(Uint8List imageBytes) => throw UnimplementedError( diff --git a/quill_native_bridge/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift index 5e6146b93..7e9a418f1 100644 --- a/quill_native_bridge/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift +++ b/quill_native_bridge/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift @@ -18,6 +18,15 @@ public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { } else { result(nil) } + case "copyHTMLToClipboard": + guard let html = call.arguments as? String else { + result(FlutterError(code: "HTML_REQUIRED", message: "HTML is required to copy the HTML to the clipboard.", details: nil)) + return + } + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString(html, forType: .html) + result(nil) case "copyImageToClipboard": guard let data = call.arguments as? FlutterStandardTypedData else { result(FlutterError(code: "IMAGE_BYTES_REQUIRED", message: "Image bytes are required to copy the image to the clipboard.", details: nil)) diff --git a/quill_native_bridge/quill_native_bridge/test/quill_native_bridge_test.dart b/quill_native_bridge/quill_native_bridge/test/quill_native_bridge_test.dart index 84cfe9f0d..261422d45 100644 --- a/quill_native_bridge/quill_native_bridge/test/quill_native_bridge_test.dart +++ b/quill_native_bridge/quill_native_bridge/test/quill_native_bridge_test.dart @@ -15,11 +15,18 @@ class MockQuillNativeBridgePlatform return '
Invalid HTML
'; } - var imageHasCopied = false; + String? primaryHTMLClipbaord; + + @override + Future copyHTMLToClipboard(String html) async { + primaryHTMLClipbaord = html; + } + + Uint8List? primaryImageClipboard; @override Future copyImageToClipboard(Uint8List imageBytes) async { - imageHasCopied = true; + primaryImageClipboard = imageBytes; } @override @@ -56,15 +63,28 @@ void main() { }); test('copyImageToClipboard()', () async { + final imageBytes = Uint8List.fromList([]); + expect( + fakePlatform.primaryImageClipboard, + null, + ); + await QuillNativeBridgePlatform.instance.copyImageToClipboard(imageBytes); + expect( + fakePlatform.primaryImageClipboard, + imageBytes, + ); + }); + + test('copyHTMLToClipboard()', () async { + const html = '
HTML
'; expect( - fakePlatform.imageHasCopied, - false, + fakePlatform.primaryHTMLClipbaord, + null, ); - await QuillNativeBridgePlatform.instance - .copyImageToClipboard(Uint8List.fromList([])); + await QuillNativeBridgePlatform.instance.copyHTMLToClipboard(html); expect( - fakePlatform.imageHasCopied, - true, + fakePlatform.primaryHTMLClipbaord, + html, ); }); diff --git a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart index eaac4c245..6b5ed289a 100644 --- a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart +++ b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart @@ -10,6 +10,9 @@ import 'package:web/web.dart'; import 'src/clipboard_api_support_unsafe.dart'; +// TODO: Reorder the methods in QuillNativeBridgeWeb to match the order of QuillNativeBridgePlatform +// and cleanup the code here, extract constants and improve the name + /// A web implementation of the [QuillNativeBridgePlatform]. /// /// **Highly Experimental** and can be removed. @@ -70,6 +73,23 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { return null; } + @override + Future copyHTMLToClipboard(String html) async { + if (isClipbaordApiUnsupported) { + throw UnsupportedError( + 'Could not copy HTML to the clipboard.\n' + 'The Clipboard API is not supported on ${window.navigator.userAgent}.\n' + 'Should fallback to Clipboard events.', + ); + } + const kMimeHtml = 'text/html'; + final blob = Blob([html.toJS].toJS, BlobPropertyBag(type: kMimeHtml)); + final clipboardItem = ClipboardItem( + {kMimeHtml.toJS: blob}.jsify() as JSObject, + ); + await window.navigator.clipboard.write([clipboardItem].toJS).toDart; + } + @override Future getClipboardImage() async { if (isClipbaordApiUnsupported) { From 4382209bf850533451f50d0ef2cfb04688cf7f01 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 00:08:00 +0300 Subject: [PATCH 31/90] chore: cleanup QuillNativeBridgeWeb, extract mime constants, organize order of implemented methods --- .../lib/quill_native_bridge_web.dart | 61 +++++++++---------- .../lib/src/mime_types_constants.dart | 2 + 2 files changed, 30 insertions(+), 33 deletions(-) create mode 100644 quill_native_bridge/quill_native_bridge_web/lib/src/mime_types_constants.dart diff --git a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart index 6b5ed289a..fc587c44c 100644 --- a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart +++ b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart @@ -9,9 +9,7 @@ import 'package:quill_native_bridge/quill_native_bridge.dart'; import 'package:web/web.dart'; import 'src/clipboard_api_support_unsafe.dart'; - -// TODO: Reorder the methods in QuillNativeBridgeWeb to match the order of QuillNativeBridgePlatform -// and cleanup the code here, extract constants and improve the name +import 'src/mime_types_constants.dart'; /// A web implementation of the [QuillNativeBridgePlatform]. /// @@ -31,27 +29,6 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { QuillNativeBridgePlatform.instance = QuillNativeBridgeWeb._(); } - @override - Future copyImageToClipboard(Uint8List imageBytes) async { - if (isClipbaordApiUnsupported) { - throw UnsupportedError( - 'Could not copy image to the clipboard.\n' - 'The Clipboard API is not supported on ${window.navigator.userAgent}.\n' - 'Should fallback to Clipboard events.', - ); - } - final blob = Blob( - [imageBytes.toJS].toJS, - BlobPropertyBag(type: 'image/png'), - ); - - final clipboardItem = ClipboardItem( - {'image/png'.toJS: blob}.jsify() as JSObject, - ); - - await window.navigator.clipboard.write([clipboardItem].toJS).toDart; - } - @override Future getClipboardHTML() async { if (isClipbaordApiUnsupported) { @@ -61,12 +38,11 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { 'Should fallback to Clipboard events.', ); } - const kMimeTextHtml = 'text/html'; final clipboardItems = (await window.navigator.clipboard.read().toDart).toDart; for (final item in clipboardItems) { - if (item.types.toDart.contains(kMimeTextHtml.toJS)) { - final html = await item.getType(kMimeTextHtml).toDart; + if (item.types.toDart.contains(kHtmlMimeType.toJS)) { + final html = await item.getType(kHtmlMimeType).toDart; return (await html.text().toDart).toDart; } } @@ -82,14 +58,34 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { 'Should fallback to Clipboard events.', ); } - const kMimeHtml = 'text/html'; - final blob = Blob([html.toJS].toJS, BlobPropertyBag(type: kMimeHtml)); + final blob = Blob([html.toJS].toJS, BlobPropertyBag(type: kHtmlMimeType)); final clipboardItem = ClipboardItem( - {kMimeHtml.toJS: blob}.jsify() as JSObject, + {kHtmlMimeType.toJS: blob}.jsify() as JSObject, ); await window.navigator.clipboard.write([clipboardItem].toJS).toDart; } + @override + Future copyImageToClipboard(Uint8List imageBytes) async { + if (isClipbaordApiUnsupported) { + throw UnsupportedError( + 'Could not copy image to the clipboard.\n' + 'The Clipboard API is not supported on ${window.navigator.userAgent}.\n' + 'Should fallback to Clipboard events.', + ); + } + final blob = Blob( + [imageBytes.toJS].toJS, + BlobPropertyBag(type: kImagePngMimeType), + ); + + final clipboardItem = ClipboardItem( + {kImagePngMimeType.toJS: blob}.jsify() as JSObject, + ); + + await window.navigator.clipboard.write([clipboardItem].toJS).toDart; + } + @override Future getClipboardImage() async { if (isClipbaordApiUnsupported) { @@ -99,12 +95,11 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { 'Should fallback to Clipboard events.', ); } - const kMimeImagePng = 'image/png'; final clipboardItems = (await window.navigator.clipboard.read().toDart).toDart; for (final item in clipboardItems) { - if (item.types.toDart.contains(kMimeImagePng.toJS)) { - final blob = await item.getType(kMimeImagePng).toDart; + if (item.types.toDart.contains(kImagePngMimeType.toJS)) { + final blob = await item.getType(kImagePngMimeType).toDart; final arrayBuffer = await blob.arrayBuffer().toDart; return arrayBuffer.toDart.asUint8List(); } diff --git a/quill_native_bridge/quill_native_bridge_web/lib/src/mime_types_constants.dart b/quill_native_bridge/quill_native_bridge_web/lib/src/mime_types_constants.dart new file mode 100644 index 000000000..43d6da3f7 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_web/lib/src/mime_types_constants.dart @@ -0,0 +1,2 @@ +const String kHtmlMimeType = 'text/html'; +const String kImagePngMimeType = 'text/png'; From f0b524176885a8d97e0f85ba4e3a6d46726dd426 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 00:09:41 +0300 Subject: [PATCH 32/90] fix(web): issue caused by previous commit --- .../quill_native_bridge_web/lib/src/mime_types_constants.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quill_native_bridge/quill_native_bridge_web/lib/src/mime_types_constants.dart b/quill_native_bridge/quill_native_bridge_web/lib/src/mime_types_constants.dart index 43d6da3f7..8a898618d 100644 --- a/quill_native_bridge/quill_native_bridge_web/lib/src/mime_types_constants.dart +++ b/quill_native_bridge/quill_native_bridge_web/lib/src/mime_types_constants.dart @@ -1,2 +1,2 @@ const String kHtmlMimeType = 'text/html'; -const String kImagePngMimeType = 'text/png'; +const String kImagePngMimeType = 'image/png'; From 2d8b135f3a7f69be38b0dfebf8696e120f7ae13c Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 00:36:37 +0300 Subject: [PATCH 33/90] docs(web): update methods of QuillNativeBridge to reflect the web support --- .../lib/quill_native_bridge.dart | 32 ++++++++++++------- ...uill_native_bridge_platform_interface.dart | 2 +- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart index 761bf5849..733e964e5 100644 --- a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart @@ -9,10 +9,6 @@ import 'src/quill_native_bridge_platform_interface.dart'; export 'src/platform_feature.dart'; export 'src/quill_native_bridge_platform_interface.dart'; -// TODO: Support description of methods here is outdated with QuillNativeBridgePlatformFeature -// web support has been added recently and still not updated. -// Remove 'non-web platforms', 'Doesn't support web' and anything related. - /// An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) /// package to access platform-specific APIs. /// @@ -22,28 +18,32 @@ class QuillNativeBridge { /// Check if the app is running on [iOS Simulator](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device). /// - /// This function should only be called when [defaultTargetPlatform] + /// Should only be called when [defaultTargetPlatform] /// is [TargetPlatform.iOS] and [kIsWeb] is `false`. static Future isIOSSimulator() => QuillNativeBridgePlatform.instance.isIOSSimulator(); - /// Return HTML from the Clipboard for **non-web platforms**. + /// Return HTML from the Clipboard. /// - /// Doesn't support web, should use - /// [paste_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event) - /// instead. + /// **Important for web**: If [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) + /// is not supported on the web browser, should fallback to [Clipboard Events](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) + /// such as the [paste_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event). /// /// The HTML can be platform-dependent. /// /// Returns `null` if the HTML content is not available or if the user has not granted /// permission for pasting (on some platforms such as iOS). /// - /// Currently only supports **Android**, **iOS** and **macOS**. + /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. static Future getClipboardHTML() => QuillNativeBridgePlatform.instance.getClipboardHTML(); /// Copy the [html] to the clipboard to be pasted on other apps. /// + /// **Important for web**: If [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) + /// is not supported on the web browser, should fallback to [Clipboard Events](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) + /// such as the [copy_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/copy_event). + /// /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. static Future copyHTMLToClipboard(String html) => QuillNativeBridgePlatform.instance.copyHTMLToClipboard(html); @@ -54,14 +54,22 @@ class QuillNativeBridge { /// Otherwise, you will get a warning available only on debug-builds. /// See: https://github.com/singerdmx/flutter-quill#-platform-specific-configurations /// + /// **Important for web**: If [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) + /// is not supported on the web browser, should fallback to [Clipboard Events](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) + /// such as the [copy_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/copy_event). + /// /// - /// Currently only supports **Android**, **iOS**, **macOS**. + /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. static Future copyImageToClipboard(Uint8List imageBytes) => QuillNativeBridgePlatform.instance.copyImageToClipboard(imageBytes); /// Return the copied image from the Clipboard. /// - /// Currently only supports **Android**, **iOS**, **macOS**. + /// **Important for web**: If [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) + /// is not supported on the web browser, should fallback to [Clipboard Events](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) + /// such as the [paste_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event). + /// + /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. static Future getClipboardImage() => QuillNativeBridgePlatform.instance.getClipboardImage(); diff --git a/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart b/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart index 10d9a443a..2322a51a9 100644 --- a/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart @@ -30,7 +30,7 @@ abstract class QuillNativeBridgePlatform extends PlatformInterface { Future isIOSSimulator() => throw UnimplementedError('isIOSSimulator() has not been implemented.'); - /// Return HTML from the Clipboard for **non-web platforms**. + /// Return HTML from the Clipboard. Future getClipboardHTML() => throw UnimplementedError('getClipboardHTML() has not been implemented.'); From 686e0ff138cfd3c97ac3e3f17a2841fd01a3600c Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 02:08:06 +0300 Subject: [PATCH 34/90] chore(android): cleanup the check for HTML from the clipboard in getClipboardHTML --- .../QuillNativeBridgePlugin.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt index a71ac574d..e42ad5371 100644 --- a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt +++ b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt @@ -38,19 +38,16 @@ class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { return } - val clipData = clipboard.primaryClip + val primaryClipData = clipboard.primaryClip - if (clipData == null || clipData.itemCount <= 0) { + if (primaryClipData == null || primaryClipData.itemCount == 0) { result.success(null) return } - val item = clipData.getItemAt(0) + val item = primaryClipData.getItemAt(0) - if (item.text == null || clipboard.primaryClipDescription?.hasMimeType( - ClipDescription.MIMETYPE_TEXT_HTML - ) == false - ) { + if (!primaryClipData.description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML)) { result.success(null) return } @@ -59,7 +56,7 @@ class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { if (htmlText == null) { result.error( "HTML_TEXT_NULL", - "Expected the HTML Text from Clipboard to be not null", + "Expected the HTML Text from the Clipboard to be not null", null ) return @@ -79,7 +76,8 @@ class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { } try { - val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipboard = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newHtmlText("HTML", html, html) clipboard.setPrimaryClip(clip) } catch (e: Exception) { From 47f05f4ca3bbe5a2eb7b0c02220600875c8f44bf Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 02:09:33 +0300 Subject: [PATCH 35/90] test: add a test for getClipboardHTML to ensure it's not null only when the html item is the last/primary --- .../integration_test/integration_test.dart | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart b/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart index 5d983da20..12da6c2b6 100644 --- a/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart +++ b/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart @@ -1,3 +1,5 @@ +import 'package:flutter/services.dart' as services + show Clipboard, ClipboardData; import 'package:flutter_test/flutter_test.dart'; import 'package:image_compare/image_compare.dart'; import 'package:integration_test/integration_test.dart'; @@ -67,5 +69,57 @@ void main() { expect(clipboardHTML, isNot(html1)); expect(clipboardHTML, html2); }); + // TODO: See if there is a need for writing a similar test for getClipboardImage + test( + 'getClipboardHTML should return the HTML content after copying HTML, ' + 'and should no longer return HTML once an image (or any non-HTML item) ' + 'has been copied to the clipboard after that.', + () async { + const html = '
HTML
'; + + // Copy HTML to clipboard before copying an image + + await QuillNativeBridge.copyHTMLToClipboard(html); + + expect( + await QuillNativeBridge.getClipboardHTML(), + html, + ); + + // Image clipboard item + final imageBytes = await loadAssetImage(kFlutterQuillAssetImage); + await QuillNativeBridge.copyImageToClipboard(imageBytes); + + expect( + await QuillNativeBridge.getClipboardHTML(), + null, + ); + + // Copy HTML to clipboard before copying plain text + + await QuillNativeBridge.copyHTMLToClipboard(html); + + expect( + await QuillNativeBridge.getClipboardHTML(), + html, + ); + + // Plain text clipboard item + const plainTextExample = 'Flutter Quill'; + services.Clipboard.setData( + const services.ClipboardData(text: plainTextExample), + ); + expect( + (await services.Clipboard.getData(services.Clipboard.kTextPlain)) + ?.text, + plainTextExample, + ); + + expect( + await QuillNativeBridge.getClipboardHTML(), + null, + ); + }, + ); }); } From 351dad884ba198c3b3a440048e09de185b92d7ff Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 02:20:43 +0300 Subject: [PATCH 36/90] ci: attempt to fix CI failure by updating outdated path of quill_native_bridge after splitting the web implementation --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f96c90d41..6642e4372 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,7 +37,7 @@ jobs: run: flutter pub get -C flutter_quill_test - name: đŸ“Ļ Install quill_native_bridge dependencies - run: flutter pub get -C quill_native_bridge + run: flutter pub get -C quill_native_bridge/quill_native_bridge - name: 🔍 Run Flutter analysis run: flutter analyze From f1c991303ba322cef77c1eab153041d694f14e17 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 03:29:27 +0300 Subject: [PATCH 37/90] docs: document why platform implementations of QuillNativeBridgePlatform should extends it rather implements --- .../src/quill_native_bridge_platform_interface.dart | 13 ++++++++++++- .../lib/quill_native_bridge_web.dart | 5 +++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart b/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart index 2322a51a9..843ebccb0 100644 --- a/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart @@ -3,7 +3,18 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'quill_native_bridge_method_channel.dart'; -/// **Experimental** as breaking changes can occur +/// **Experimental** as breaking changes can occur. +/// +/// Platform implementations should extend this class rather than implement it +/// as newly added methods are not considered to be breaking +/// changes. Extending this class (using `extends`) ensures that the subclass +/// will get the default implementation, while platform implementations that +/// `implements` this interface will be broken by newly added +/// [QuillNativeBridgePlatform] methods. +/// +/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) +/// and [plugin_platform_interface](https://pub.dev/packages/plugin_platform_interface) +/// for more details. abstract class QuillNativeBridgePlatform extends PlatformInterface { /// Constructs a QuillNativeBridgePlatform. QuillNativeBridgePlatform() : super(token: _token); diff --git a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart index fc587c44c..ac80726d9 100644 --- a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart +++ b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart @@ -19,9 +19,10 @@ import 'src/mime_types_constants.dart'; /// /// ```console /// Assertion failed: "Platform interfaces must not be implemented with `implements`" -/// -/// See https://github.com/flutter/flutter/issues/127396 /// ``` +/// +/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) +/// and [QuillNativeBridgePlatform] for more details. class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { QuillNativeBridgeWeb._(); From 4e65d65d182561bb21b53a57ed64163d790b0a7a Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 14:38:19 +0300 Subject: [PATCH 38/90] chore(example): add windows platform runner example using Flutter CLI (change is automated) --- .../quill_native_bridge/example/.metadata | 30 ++ .../example/windows/.gitignore | 17 ++ .../example/windows/CMakeLists.txt | 108 +++++++ .../example/windows/flutter/CMakeLists.txt | 109 +++++++ .../flutter/generated_plugin_registrant.cc | 11 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 23 ++ .../example/windows/runner/CMakeLists.txt | 40 +++ .../example/windows/runner/Runner.rc | 121 ++++++++ .../example/windows/runner/flutter_window.cpp | 71 +++++ .../example/windows/runner/flutter_window.h | 33 ++ .../example/windows/runner/main.cpp | 43 +++ .../example/windows/runner/resource.h | 16 + .../windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes .../windows/runner/runner.exe.manifest | 14 + .../example/windows/runner/utils.cpp | 65 ++++ .../example/windows/runner/utils.h | 19 ++ .../example/windows/runner/win32_window.cpp | 288 ++++++++++++++++++ .../example/windows/runner/win32_window.h | 102 +++++++ 19 files changed, 1125 insertions(+) create mode 100644 quill_native_bridge/quill_native_bridge/example/.metadata create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/.gitignore create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/CMakeLists.txt create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/flutter/CMakeLists.txt create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.cc create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.h create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugins.cmake create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/CMakeLists.txt create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/Runner.rc create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.cpp create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.h create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/main.cpp create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/resource.h create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/resources/app_icon.ico create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/runner.exe.manifest create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/utils.cpp create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/utils.h create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.cpp create mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.h diff --git a/quill_native_bridge/quill_native_bridge/example/.metadata b/quill_native_bridge/quill_native_bridge/example/.metadata new file mode 100644 index 000000000..ea59ad0bc --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/.metadata @@ -0,0 +1,30 @@ +# 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: "2663184aa79047d0a33a14a3b607954f8fdd8730" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: windows + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + + # 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/quill_native_bridge/quill_native_bridge/example/windows/.gitignore b/quill_native_bridge/quill_native_bridge/example/windows/.gitignore new file mode 100644 index 000000000..d492d0d98 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/quill_native_bridge/quill_native_bridge/example/windows/CMakeLists.txt b/quill_native_bridge/quill_native_bridge/example/windows/CMakeLists.txt new file mode 100644 index 000000000..d960948af --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/quill_native_bridge/quill_native_bridge/example/windows/flutter/CMakeLists.txt b/quill_native_bridge/quill_native_bridge/example/windows/flutter/CMakeLists.txt new file mode 100644 index 000000000..903f4899d --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.cc b/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..8b6d4680a --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.h b/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..dc139d85a --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugins.cmake b/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000..b93c4c30c --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/CMakeLists.txt b/quill_native_bridge/quill_native_bridge/example/windows/runner/CMakeLists.txt new file mode 100644 index 000000000..394917c05 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/Runner.rc b/quill_native_bridge/quill_native_bridge/example/windows/runner/Runner.rc new file mode 100644 index 000000000..54797c97d --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "dev.flutterquill" "\0" + VALUE "FileDescription", "example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 dev.flutterquill. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.cpp b/quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.cpp new file mode 100644 index 000000000..955ee3038 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.h b/quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.h new file mode 100644 index 000000000..6da0652f0 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/main.cpp b/quill_native_bridge/quill_native_bridge/example/windows/runner/main.cpp new file mode 100644 index 000000000..a61bf80d3 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/resource.h b/quill_native_bridge/quill_native_bridge/example/windows/runner/resource.h new file mode 100644 index 000000000..66a65d1e4 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/resources/app_icon.ico b/quill_native_bridge/quill_native_bridge/example/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/runner.exe.manifest b/quill_native_bridge/quill_native_bridge/example/windows/runner/runner.exe.manifest new file mode 100644 index 000000000..153653e8d --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/utils.cpp b/quill_native_bridge/quill_native_bridge/example/windows/runner/utils.cpp new file mode 100644 index 000000000..3a0b46511 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/utils.h b/quill_native_bridge/quill_native_bridge/example/windows/runner/utils.h new file mode 100644 index 000000000..3879d5475 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.cpp b/quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.cpp new file mode 100644 index 000000000..60608d0fe --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.h b/quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.h new file mode 100644 index 000000000..e901dde68 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ From 9300c4a2053777f2abc57f678358068e08ea2a16 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 17:59:44 +0300 Subject: [PATCH 39/90] feat: (WIP) add windows experimental support for getClipboardHTML(), new integration test to ensure the HTML is parsable --- .../integration_test/integration_test.dart | 27 +++++ .../quill_native_bridge/example/pubspec.yaml | 2 + .../lib/quill_native_bridge.dart | 2 +- .../lib/src/platform_feature.dart | 8 +- .../quill_native_bridge/pubspec.yaml | 6 +- .../pubspec_overrides.yaml | 4 +- .../quill_native_bridge_windows/CHANGELOG.md | 8 ++ .../quill_native_bridge_windows/LICENSE | 21 ++++ .../quill_native_bridge_windows/README.md | 13 +++ .../lib/quill_native_bridge_windows.dart | 99 +++++++++++++++++++ .../lib/src/html_cleaner.dart | 69 +++++++++++++ .../quill_native_bridge_windows/pubspec.yaml | 32 ++++++ .../pubspec_overrides.yaml | 4 + .../test/html_cleaner_test.dart | 50 ++++++++++ 14 files changed, 340 insertions(+), 5 deletions(-) create mode 100644 quill_native_bridge/quill_native_bridge_windows/CHANGELOG.md create mode 100644 quill_native_bridge/quill_native_bridge_windows/LICENSE create mode 100644 quill_native_bridge/quill_native_bridge_windows/README.md create mode 100644 quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart create mode 100644 quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart create mode 100644 quill_native_bridge/quill_native_bridge_windows/pubspec.yaml create mode 100644 quill_native_bridge/quill_native_bridge_windows/pubspec_overrides.yaml create mode 100644 quill_native_bridge/quill_native_bridge_windows/test/html_cleaner_test.dart diff --git a/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart b/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart index 12da6c2b6..b5b98fa0e 100644 --- a/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart +++ b/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart @@ -121,5 +121,32 @@ void main() { ); }, ); + + // Some platforms such as windows might include comments/description + // that can make the HTML invalid + test( + 'should return valid HTML that can be parsed', + () async { + const exampleHtml = '
HTML Div
'; + + await QuillNativeBridge.copyHTMLToClipboard(exampleHtml); + final clipboardHtml = await QuillNativeBridge.getClipboardHTML(); + + if (clipboardHtml == null) { + fail( + 'Html has been copied to the clipboard and expected to be not null.', + ); + } + + bool isHTML(String str) { + final htmlRegExp = + RegExp('<[^>]*>', multiLine: true, caseSensitive: false); + return htmlRegExp.hasMatch(str) && str.startsWith('<'); + } + + expect(isHTML(clipboardHtml), true); + expect(isHTML('Invalid'), false); + }, + ); }); } diff --git a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml index 28aac57f8..ffee49cc2 100644 --- a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml @@ -16,6 +16,8 @@ dependency_overrides: path: ../ quill_native_bridge_web: path: ../../quill_native_bridge_web + quill_native_bridge_windows: + path: ../../quill_native_bridge_windows dev_dependencies: integration_test: diff --git a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart index 733e964e5..d1f5f49ee 100644 --- a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart @@ -34,7 +34,7 @@ class QuillNativeBridge { /// Returns `null` if the HTML content is not available or if the user has not granted /// permission for pasting (on some platforms such as iOS). /// - /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. + /// Currently only supports **Android**, **iOS**, **macOS**, **Windows** and **Web**. static Future getClipboardHTML() => QuillNativeBridgePlatform.instance.getClipboardHTML(); diff --git a/quill_native_bridge/quill_native_bridge/lib/src/platform_feature.dart b/quill_native_bridge/quill_native_bridge/lib/src/platform_feature.dart index 93a5b0cd2..30dff2cf3 100644 --- a/quill_native_bridge/quill_native_bridge/lib/src/platform_feature.dart +++ b/quill_native_bridge/quill_native_bridge/lib/src/platform_feature.dart @@ -46,8 +46,12 @@ enum QuillNativeBridgePlatformFeature { QuillNativeBridgePlatformFeature.isIOSSimulator => !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS, QuillNativeBridgePlatformFeature.getClipboardHTML => kIsWeb || - {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} - .contains(defaultTargetPlatform), + { + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + TargetPlatform.windows, + }.contains(defaultTargetPlatform), QuillNativeBridgePlatformFeature.copyHTMLToClipboard => kIsWeb || {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} .contains(defaultTargetPlatform), diff --git a/quill_native_bridge/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml index e18aca99b..d49ee7c29 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec.yaml @@ -21,6 +21,8 @@ dependencies: sdk: flutter plugin_platform_interface: ^2.1.8 quill_native_bridge_web: ^0.0.1-dev.0 + quill_native_bridge_windows: + path: ../quill_native_bridge_windows dev_dependencies: flutter_test: @@ -38,4 +40,6 @@ flutter: macos: pluginClass: QuillNativeBridgePlugin web: - default_package: quill_native_bridge_web \ No newline at end of file + default_package: quill_native_bridge_web + windows: + default_package: quill_native_bridge_windows \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml index 2a01ffe68..318cf5de8 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml @@ -1,4 +1,6 @@ # TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing dependency_overrides: quill_native_bridge_web: - path: ../quill_native_bridge_web \ No newline at end of file + path: ../quill_native_bridge_web + quill_native_bridge_windows: + path: ../quill_native_bridge_windows \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_windows/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_windows/CHANGELOG.md new file mode 100644 index 000000000..167a42b26 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_windows/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +All notable changes to this project will be documented in this file. + + +## 0.0.1-dev.0 + +- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_windows/LICENSE b/quill_native_bridge/quill_native_bridge_windows/LICENSE new file mode 100644 index 000000000..e7ff73e1b --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_windows/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_windows/README.md b/quill_native_bridge/quill_native_bridge_windows/README.md new file mode 100644 index 000000000..497a48e89 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_windows/README.md @@ -0,0 +1,13 @@ +# đŸĒļ Quill Native Bridge + +The Web implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). + +## ⚙ī¸ Usage + +This package is endorsed, which means you can simply use `quill_native_bridge` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. + +However, if you import this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. + +## 📉 Note on breaking changes + +The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart new file mode 100644 index 000000000..7aa1c1312 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -0,0 +1,99 @@ +// This file is referenced by pubspec.yaml. If you plan on moving this file +// Make sure to update pubspec.yaml to the new location. + +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; +import 'package:quill_native_bridge/quill_native_bridge.dart'; +import 'package:win32/win32.dart'; + +import 'src/html_cleaner.dart'; + +/// A Windows implementation of the [QuillNativeBridgePlatform]. +/// +/// **Highly Experimental** and can be removed. +/// +/// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: +/// +/// ```console +/// Assertion failed: "Platform interfaces must not be implemented with `implements`" +/// ``` +/// +/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) +/// and [QuillNativeBridgePlatform] for more details. +/// ``` +class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { + QuillNativeBridgeWindows._(); + + static void registerWith() { + QuillNativeBridgePlatform.instance = QuillNativeBridgeWindows._(); + } + + // TODO: Cleanup this code here + + // TODO: Improve error handling by throwing exception + // instead of using assert, should have a proper way of handling + // errors regardless of this implementation. + + // TODO: Throw exception and always close the clipboard at once + // regardless of the result + + // TODO: Test Clipboard operations with other windows apps and + // see if this implementation causing issues + + /// From [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format). + static const _kHtmlFormatName = 'HTML Format'; + + @override + Future getClipboardHTML() async { + if (OpenClipboard(NULL) == FALSE) { + assert(false, 'Unknown error while opening the clipboard.'); + return null; + } + + final htmlFormatPointer = _kHtmlFormatName.toNativeUtf16(); + final htmlFormatId = RegisterClipboardFormat(htmlFormatPointer); + calloc.free(htmlFormatPointer); + + if (htmlFormatId == 0) { + CloseClipboard(); + assert(false, 'Failed to register clipboard HTML format.'); + return null; + } + + if (IsClipboardFormatAvailable(htmlFormatId) == FALSE) { + CloseClipboard(); + return null; + } + + final clipboardDataHandle = GetClipboardData(htmlFormatId); + if (clipboardDataHandle == NULL) { + CloseClipboard(); + assert(false, 'Failed to get clipboard data.'); + return null; + } + + final clipboardDataPointer = Pointer.fromAddress(clipboardDataHandle); + final lockedMemoryPointer = GlobalLock(clipboardDataPointer); + if (lockedMemoryPointer == nullptr) { + CloseClipboard(); + assert( + false, + 'Failed to lock global memory. Error code: ${GetLastError()}', + ); + return null; + } + + final windowsHtmlWithMetadata = + lockedMemoryPointer.cast().toDartString(); + GlobalUnlock(clipboardDataPointer); + CloseClipboard(); + + // Strip comments at the start of the HTML as they can cause + // issues while parsing the HTML + + final cleanedHtml = stripWin32HtmlDescription(windowsHtmlWithMetadata); + + return cleanedHtml; + } +} diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart b/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart new file mode 100644 index 000000000..2bc172338 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart @@ -0,0 +1,69 @@ +/// [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format) +const _kWindowsMetadataHtmlKeys = { + 'Version', + 'StartHTML', + 'EndHTML', + 'StartFragment', + 'EndFragment', + 'StartSelection', + 'EndSelection' +}; + +/// Remove the leading description from Windows clipboard HTML. +/// +/// This function targets specific metadata keys that precede the actual HTML content: +/// - `Version` +/// - `StartHTML` +/// - `EndHTML` +/// - `StartFragment` +/// - `EndFragment` +/// - `StartSelection` +/// - `EndSelection` +/// +/// These keys are not valid HTML and should be removed for proper parsing. +/// +/// This function assumes that the metadata block appears before +/// the actual HTML content and that it's formatted consistently with keys +/// followed by values. +/// +/// [html] The HTML content retrieved from the clipboard, which includes the metadata. +/// +/// Example of the original (dirty) HTML: +/// +/// ```html +/// Version:0.9 +/// StartHTML:0000000105 +/// EndHTML:0000000634 +/// StartFragment:0000000141 +/// EndFragment:0000000598 +/// +/// +///
Example HTML
+/// +/// +/// ``` +/// +/// Refer to [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format) +/// for details. +String stripWin32HtmlDescription(String html) { + // Can contains dirty lines + final lines = html.split('\n'); + + final cleanedLines = [...lines]; + + for (final line in lines) { + // Stop processing when reaching the start of actual HTML content + if (line.startsWith('')) { + break; + } + + final isWindowsHtmlMetadata = _kWindowsMetadataHtmlKeys + .any((metadataKey) => line.startsWith('$metadataKey:')); + if (isWindowsHtmlMetadata) { + cleanedLines.remove(line); + continue; + } + } + + return cleanedLines.join('\n'); +} diff --git a/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml b/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml new file mode 100644 index 000000000..e512d8818 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml @@ -0,0 +1,32 @@ +name: quill_native_bridge_windows +description: "Windows implementation of the quill_native_bridge plugin." +version: 0.0.1-dev.0 +homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_windows +repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_windows +issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ +documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_windows + +environment: + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.0.0' + +dependencies: + flutter: + sdk: flutter + quill_native_bridge: ^10.7.4 + win32: ^5.5.4 + ffi: ^2.1.3 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + +flutter: + plugin: + implements: quill_native_bridge + platforms: + windows: + pluginClass: none + dartPluginClass: QuillNativeBridgeWindows + fileName: quill_native_bridge_windows.dart diff --git a/quill_native_bridge/quill_native_bridge_windows/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_windows/pubspec_overrides.yaml new file mode 100644 index 000000000..477638a80 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_windows/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing +dependency_overrides: + quill_native_bridge: + path: ../quill_native_bridge \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_windows/test/html_cleaner_test.dart b/quill_native_bridge/quill_native_bridge_windows/test/html_cleaner_test.dart new file mode 100644 index 000000000..d38082cc9 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_windows/test/html_cleaner_test.dart @@ -0,0 +1,50 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:quill_native_bridge_windows/src/html_cleaner.dart'; + +void main() { + group('stripWin32HtmlDescription', () { + test( + 'should remove windows metadata block from clipboard HTML', + () { + const windowsClipboardHtmlExample = ''' +Version:0.9 +StartHTML:0000000105 +EndHTML:0000000634 +StartFragment:0000000141 +EndFragment:0000000598 + + +
      return null;
+ + +'''; + const expectedHtml = ''' + + +
      return null;
+ + +'''; + final strippedHtml = + stripWin32HtmlDescription(windowsClipboardHtmlExample); + expect( + strippedHtml, + expectedHtml, + ); + expect(strippedHtml.trim(), startsWith('')); + expect(strippedHtml.trim(), endsWith('')); + }, + ); + + test('should return original HTML if no metadata is found', () { + const cleanHtml = ''' + + +
Some clean HTML content
+ +'''; + + expect(stripWin32HtmlDescription(cleanHtml), equals(cleanHtml)); + }); + }); +} From 219ee7bac86d8c354ee79e55f04d2791499e9d05 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 18:03:55 +0300 Subject: [PATCH 40/90] fix(docs): update incorrect description of quill_native_bridge_windows in README --- quill_native_bridge/quill_native_bridge_windows/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quill_native_bridge/quill_native_bridge_windows/README.md b/quill_native_bridge/quill_native_bridge_windows/README.md index 497a48e89..0b4cb4f10 100644 --- a/quill_native_bridge/quill_native_bridge_windows/README.md +++ b/quill_native_bridge/quill_native_bridge_windows/README.md @@ -1,6 +1,6 @@ # đŸĒļ Quill Native Bridge -The Web implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). +The Windows implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). ## ⚙ī¸ Usage From c036553fc99a750f2bb3c9bba7055a5eca194690 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 19:53:00 +0300 Subject: [PATCH 41/90] refactor: move plugin platform interface to quill_native_bridge_platform_interface --- pubspec.yaml | 2 +- .../quill_native_bridge/CHANGELOG.md | 5 ++++ .../quill_native_bridge/example/pubspec.yaml | 2 ++ .../lib/quill_native_bridge.dart | 26 +++++++++---------- .../quill_native_bridge/pubspec.yaml | 7 +++-- .../CHANGELOG.md | 8 ++++++ .../LICENSE | 21 +++++++++++++++ .../README.md | 17 ++++++++++++ ...uill_native_bridge_platform_interface.dart | 2 +- .../lib/src/platform_feature.dart | 0 .../quill_native_bridge_method_channel.dart | 4 ++- .../pubspec.yaml | 21 +++++++++++++++ ...ill_native_bridge_method_channel_test.dart | 2 +- .../test/quill_native_bridge_test.dart | 6 ++--- .../lib/quill_native_bridge_web.dart | 2 +- .../quill_native_bridge_web/pubspec.yaml | 2 +- .../pubspec_overrides.yaml | 4 +-- .../lib/quill_native_bridge_windows.dart | 2 +- .../quill_native_bridge_windows/pubspec.yaml | 2 +- .../pubspec_overrides.yaml | 4 +-- 20 files changed, 107 insertions(+), 32 deletions(-) create mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/CHANGELOG.md create mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/LICENSE create mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/README.md rename quill_native_bridge/{quill_native_bridge/lib/src => quill_native_bridge_platform_interface/lib}/quill_native_bridge_platform_interface.dart (98%) rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_platform_interface}/lib/src/platform_feature.dart (100%) rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_platform_interface}/lib/src/quill_native_bridge_method_channel.dart (97%) create mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/pubspec.yaml rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_platform_interface}/test/quill_native_bridge_method_channel_test.dart (88%) rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_platform_interface}/test/quill_native_bridge_test.dart (90%) diff --git a/pubspec.yaml b/pubspec.yaml index 1bfa17ed9..758dc5a01 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: # Plugins url_launcher: ^6.2.4 flutter_keyboard_visibility: ^6.0.0 - quill_native_bridge: ^10.7.4 + quill_native_bridge: ^10.7.5-dev.0 dev_dependencies: flutter_lints: ^4.0.0 diff --git a/quill_native_bridge/quill_native_bridge/CHANGELOG.md b/quill_native_bridge/quill_native_bridge/CHANGELOG.md index 6b79d21d4..7168bbd70 100644 --- a/quill_native_bridge/quill_native_bridge/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. +## 10.7.5-dev.0 + +- Move the plugin platform interface `QuillNativeBridgePlatform` to [quill_native_bridge_platform_interface](https://pub.dev/packages/quill_native_bridge_platform_interface). +- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. + ## 10.7.4 - Seperate the version of `quill_native_bridge` from `flutter_quill` and related packages. diff --git a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml index ffee49cc2..93ac83c97 100644 --- a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: dependency_overrides: quill_native_bridge: path: ../ + quill_native_bridge_platform_interface: + path: ../../quill_native_bridge_platform_interface quill_native_bridge_web: path: ../../quill_native_bridge_web quill_native_bridge_windows: diff --git a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart index d1f5f49ee..bb964b5d1 100644 --- a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart @@ -3,11 +3,11 @@ library; import 'package:flutter/foundation.dart' show TargetPlatform, Uint8List, defaultTargetPlatform, kIsWeb; -import 'src/platform_feature.dart'; -import 'src/quill_native_bridge_platform_interface.dart'; +import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; -export 'src/platform_feature.dart'; -export 'src/quill_native_bridge_platform_interface.dart'; +// TODO: Might move platform feature check outside of quill_native_bridge_platform_interface +// to allow the implementation of QuillNativeBridgePlatform to have a different check. +export 'package:quill_native_bridge_platform_interface/src/platform_feature.dart'; /// An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) /// package to access platform-specific APIs. @@ -16,12 +16,14 @@ export 'src/quill_native_bridge_platform_interface.dart'; class QuillNativeBridge { QuillNativeBridge._(); + static QuillNativeBridgePlatform get _platform => + QuillNativeBridgePlatform.instance; + /// Check if the app is running on [iOS Simulator](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device). /// /// Should only be called when [defaultTargetPlatform] /// is [TargetPlatform.iOS] and [kIsWeb] is `false`. - static Future isIOSSimulator() => - QuillNativeBridgePlatform.instance.isIOSSimulator(); + static Future isIOSSimulator() => _platform.isIOSSimulator(); /// Return HTML from the Clipboard. /// @@ -35,8 +37,7 @@ class QuillNativeBridge { /// permission for pasting (on some platforms such as iOS). /// /// Currently only supports **Android**, **iOS**, **macOS**, **Windows** and **Web**. - static Future getClipboardHTML() => - QuillNativeBridgePlatform.instance.getClipboardHTML(); + static Future getClipboardHTML() => _platform.getClipboardHTML(); /// Copy the [html] to the clipboard to be pasted on other apps. /// @@ -46,7 +47,7 @@ class QuillNativeBridge { /// /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. static Future copyHTMLToClipboard(String html) => - QuillNativeBridgePlatform.instance.copyHTMLToClipboard(html); + _platform.copyHTMLToClipboard(html); /// Copy the [imageBytes] to Clipboard to be pasted on other apps. /// @@ -61,7 +62,7 @@ class QuillNativeBridge { /// /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. static Future copyImageToClipboard(Uint8List imageBytes) => - QuillNativeBridgePlatform.instance.copyImageToClipboard(imageBytes); + _platform.copyImageToClipboard(imageBytes); /// Return the copied image from the Clipboard. /// @@ -71,11 +72,10 @@ class QuillNativeBridge { /// /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. static Future getClipboardImage() => - QuillNativeBridgePlatform.instance.getClipboardImage(); + _platform.getClipboardImage(); /// Return the copied gif from the Clipboard. /// /// Currently only supports **Android**, **iOS**. - static Future getClipboardGif() => - QuillNativeBridgePlatform.instance.getClipboardGif(); + static Future getClipboardGif() => _platform.getClipboardGif(); } diff --git a/quill_native_bridge/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml index d49ee7c29..f19e6a5ac 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge description: "An internal plugin for flutter_quill package to access platform-specific APIs" -version: 10.7.4 +version: 10.7.5-dev.0 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -19,10 +19,9 @@ environment: dependencies: flutter: sdk: flutter - plugin_platform_interface: ^2.1.8 + quill_native_bridge_platform_interface: ^0.0.1-dev.0 quill_native_bridge_web: ^0.0.1-dev.0 - quill_native_bridge_windows: - path: ../quill_native_bridge_windows + quill_native_bridge_windows: ^0.0.1-dev.0 dev_dependencies: flutter_test: diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_platform_interface/CHANGELOG.md new file mode 100644 index 000000000..167a42b26 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_platform_interface/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +All notable changes to this project will be documented in this file. + + +## 0.0.1-dev.0 + +- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/LICENSE b/quill_native_bridge/quill_native_bridge_platform_interface/LICENSE new file mode 100644 index 000000000..e7ff73e1b --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_platform_interface/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_platform_interface/README.md b/quill_native_bridge/quill_native_bridge_platform_interface/README.md new file mode 100644 index 000000000..99150a14a --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_platform_interface/README.md @@ -0,0 +1,17 @@ +# đŸĒļ Quill Native Bridge + +A common platform interface for the [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge) plugin. + +This interface allows platform-specific implementations of the `quill_native_bridge` plugin, as well as the plugin itself, to ensure they are supporting the same interface. + +## ⚙ī¸ Usage + +To implement a new platform-specific implementation of `quill_native_bridge`, extend [`QuillNativeBridgePlatform`](./lib/quill_native_bridge_platform_interface.dart) with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `QuillNativeBridgePlatform` by calling: + +```dart +QuillNativeBridgePlatform.instance = MyPlatformQuillNativeBridge(); +``` + +## 📉 Note on breaking changes + +The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart similarity index 98% rename from quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart rename to quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart index 843ebccb0..3a6015dd5 100644 --- a/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart' show Uint8List; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'quill_native_bridge_method_channel.dart'; +import 'src/quill_native_bridge_method_channel.dart'; /// **Experimental** as breaking changes can occur. /// diff --git a/quill_native_bridge/quill_native_bridge/lib/src/platform_feature.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart similarity index 100% rename from quill_native_bridge/quill_native_bridge/lib/src/platform_feature.dart rename to quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart diff --git a/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart similarity index 97% rename from quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart rename to quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart index 5a2cac142..688d89db8 100644 --- a/quill_native_bridge/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart @@ -1,9 +1,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart' show MethodChannel, PlatformException; +import '../quill_native_bridge_platform_interface.dart'; import 'platform_feature.dart'; -import 'quill_native_bridge_platform_interface.dart'; +/// A default [QuillNativeBridgePlatform] implementation backed by a platform +/// channel. class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { @visibleForTesting final methodChannel = const MethodChannel('quill_native_bridge'); diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/pubspec.yaml b/quill_native_bridge/quill_native_bridge_platform_interface/pubspec.yaml new file mode 100644 index 000000000..34ccd1e71 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_platform_interface/pubspec.yaml @@ -0,0 +1,21 @@ +name: quill_native_bridge_platform_interface +description: "A common platform interface for the quill_native_bridge plugin." +version: 0.0.1-dev.0 +homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_platform_interface +repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_platform_interface +issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ +documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_platform_interface + +environment: + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.0.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.1.8 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/test/quill_native_bridge_method_channel_test.dart b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_method_channel_test.dart similarity index 88% rename from quill_native_bridge/quill_native_bridge/test/quill_native_bridge_method_channel_test.dart rename to quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_method_channel_test.dart index d0e1bbd46..341cedd11 100644 --- a/quill_native_bridge/quill_native_bridge/test/quill_native_bridge_method_channel_test.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_method_channel_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:quill_native_bridge/src/quill_native_bridge_method_channel.dart'; +import 'package:quill_native_bridge_platform_interface/src/quill_native_bridge_method_channel.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); diff --git a/quill_native_bridge/quill_native_bridge/test/quill_native_bridge_test.dart b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart similarity index 90% rename from quill_native_bridge/quill_native_bridge/test/quill_native_bridge_test.dart rename to quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart index 261422d45..60401edb1 100644 --- a/quill_native_bridge/quill_native_bridge/test/quill_native_bridge_test.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart @@ -1,8 +1,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'package:quill_native_bridge/quill_native_bridge.dart'; -import 'package:quill_native_bridge/src/quill_native_bridge_method_channel.dart'; +import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; +import 'package:quill_native_bridge_platform_interface/src/quill_native_bridge_method_channel.dart'; class MockQuillNativeBridgePlatform with MockPlatformInterfaceMixin @@ -52,7 +52,7 @@ void main() { test('isIOSSimulator', () async { debugDefaultTargetPlatformOverride = TargetPlatform.iOS; - expect(await QuillNativeBridge.isIOSSimulator(), false); + expect(await QuillNativeBridgePlatform.instance.isIOSSimulator(), false); }); test('getClipboardHTML()', () async { diff --git a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart index ac80726d9..ace2df795 100644 --- a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart +++ b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart @@ -5,7 +5,7 @@ import 'dart:js_interop'; import 'package:flutter/foundation.dart' show Uint8List, debugPrint; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; -import 'package:quill_native_bridge/quill_native_bridge.dart'; +import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; import 'package:web/web.dart'; import 'src/clipboard_api_support_unsafe.dart'; diff --git a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml index 737e90dbd..4e070429c 100644 --- a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: flutter_web_plugins: sdk: flutter web: ^1.0.0 - quill_native_bridge: ^10.7.4 + quill_native_bridge_platform_interface: ^0.0.1-dev.0 dev_dependencies: flutter_test: diff --git a/quill_native_bridge/quill_native_bridge_web/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_web/pubspec_overrides.yaml index 477638a80..a07f95b27 100644 --- a/quill_native_bridge/quill_native_bridge_web/pubspec_overrides.yaml +++ b/quill_native_bridge/quill_native_bridge_web/pubspec_overrides.yaml @@ -1,4 +1,4 @@ # TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing dependency_overrides: - quill_native_bridge: - path: ../quill_native_bridge \ No newline at end of file + quill_native_bridge_platform_interface: + path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index 7aa1c1312..d94e75b89 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -4,7 +4,7 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; -import 'package:quill_native_bridge/quill_native_bridge.dart'; +import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; import 'package:win32/win32.dart'; import 'src/html_cleaner.dart'; diff --git a/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml b/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml index e512d8818..73c06dd42 100644 --- a/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml @@ -13,7 +13,7 @@ environment: dependencies: flutter: sdk: flutter - quill_native_bridge: ^10.7.4 + quill_native_bridge_platform_interface: ^0.0.1-dev.0 win32: ^5.5.4 ffi: ^2.1.3 diff --git a/quill_native_bridge/quill_native_bridge_windows/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_windows/pubspec_overrides.yaml index 477638a80..a07f95b27 100644 --- a/quill_native_bridge/quill_native_bridge_windows/pubspec_overrides.yaml +++ b/quill_native_bridge/quill_native_bridge_windows/pubspec_overrides.yaml @@ -1,4 +1,4 @@ # TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing dependency_overrides: - quill_native_bridge: - path: ../quill_native_bridge \ No newline at end of file + quill_native_bridge_platform_interface: + path: ../quill_native_bridge_platform_interface \ No newline at end of file From 1e4d0e290e13f7c143b5a339c61324c3b2cb5019 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 20:31:19 +0300 Subject: [PATCH 42/90] chore: (WIP): publish quill_native_bridge 10.7.5 --- pubspec.yaml | 2 +- quill_native_bridge/quill_native_bridge/CHANGELOG.md | 4 ++++ quill_native_bridge/quill_native_bridge/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 758dc5a01..a9f5c8c3a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: # Plugins url_launcher: ^6.2.4 flutter_keyboard_visibility: ^6.0.0 - quill_native_bridge: ^10.7.5-dev.0 + quill_native_bridge: ^10.7.5 dev_dependencies: flutter_lints: ^4.0.0 diff --git a/quill_native_bridge/quill_native_bridge/CHANGELOG.md b/quill_native_bridge/quill_native_bridge/CHANGELOG.md index 7168bbd70..713db3b9c 100644 --- a/quill_native_bridge/quill_native_bridge/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 10.7.5 + +- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. + ## 10.7.5-dev.0 - Move the plugin platform interface `QuillNativeBridgePlatform` to [quill_native_bridge_platform_interface](https://pub.dev/packages/quill_native_bridge_platform_interface). diff --git a/quill_native_bridge/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml index f19e6a5ac..921e97dba 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge description: "An internal plugin for flutter_quill package to access platform-specific APIs" -version: 10.7.5-dev.0 +version: 10.7.5 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ From 217ef1a5e753d4d78fa7f0ed86b4dfe72420e9ab Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 20:39:24 +0300 Subject: [PATCH 43/90] chore: (WIP) publish quill_native_bridge_web to use quill_native_bridge_platform_interface --- pubspec.yaml | 2 +- quill_native_bridge/quill_native_bridge/CHANGELOG.md | 5 +++++ quill_native_bridge/quill_native_bridge/pubspec.yaml | 4 ++-- quill_native_bridge/quill_native_bridge_web/CHANGELOG.md | 5 +++++ quill_native_bridge/quill_native_bridge_web/pubspec.yaml | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index a9f5c8c3a..493166a02 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: # Plugins url_launcher: ^6.2.4 flutter_keyboard_visibility: ^6.0.0 - quill_native_bridge: ^10.7.5 + quill_native_bridge: ^10.7.6 dev_dependencies: flutter_lints: ^4.0.0 diff --git a/quill_native_bridge/quill_native_bridge/CHANGELOG.md b/quill_native_bridge/quill_native_bridge/CHANGELOG.md index 713db3b9c..28c1456de 100644 --- a/quill_native_bridge/quill_native_bridge/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. +## 10.7.6 + +- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. +- Update `quill_native_bridge_web` + ## 10.7.5 - Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. diff --git a/quill_native_bridge/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml index 921e97dba..3d350a246 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge description: "An internal plugin for flutter_quill package to access platform-specific APIs" -version: 10.7.5 +version: 10.7.6 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -20,7 +20,7 @@ dependencies: flutter: sdk: flutter quill_native_bridge_platform_interface: ^0.0.1-dev.0 - quill_native_bridge_web: ^0.0.1-dev.0 + quill_native_bridge_web: ^0.0.1-dev.1 quill_native_bridge_windows: ^0.0.1-dev.0 dev_dependencies: diff --git a/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md index 167a42b26..58e751774 100644 --- a/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. +## 0.0.1-dev.1 + +- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. +- Use [`quill_native_bridge_platform_interface`](https://pub.dev/packages/quill_native_bridge_platform_interface) instead of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge) + ## 0.0.1-dev.0 diff --git a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml index 4e070429c..fe2bbe6c1 100644 --- a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge_web description: "Web platform implementation of quill_native_bridge" -version: 0.0.1-dev.0 +version: 0.0.1-dev.1 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ From e49d6f31f0570a1a3caffef66a74025f1b27b2f0 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 23 Sep 2024 15:33:52 -0700 Subject: [PATCH 44/90] chore(example): add linux platform runner example using Flutter CLI (change is automated) --- .../quill_native_bridge/example/.metadata | 2 +- .../example/linux/.gitignore | 1 + .../example/linux/CMakeLists.txt | 145 ++++++++++++++++++ .../example/linux/flutter/CMakeLists.txt | 88 +++++++++++ .../flutter/generated_plugin_registrant.cc | 11 ++ .../flutter/generated_plugin_registrant.h | 15 ++ .../linux/flutter/generated_plugins.cmake | 23 +++ .../quill_native_bridge/example/linux/main.cc | 6 + .../example/linux/my_application.cc | 124 +++++++++++++++ .../example/linux/my_application.h | 18 +++ 10 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 quill_native_bridge/quill_native_bridge/example/linux/.gitignore create mode 100644 quill_native_bridge/quill_native_bridge/example/linux/CMakeLists.txt create mode 100644 quill_native_bridge/quill_native_bridge/example/linux/flutter/CMakeLists.txt create mode 100644 quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.cc create mode 100644 quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.h create mode 100644 quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugins.cmake create mode 100644 quill_native_bridge/quill_native_bridge/example/linux/main.cc create mode 100644 quill_native_bridge/quill_native_bridge/example/linux/my_application.cc create mode 100644 quill_native_bridge/quill_native_bridge/example/linux/my_application.h diff --git a/quill_native_bridge/quill_native_bridge/example/.metadata b/quill_native_bridge/quill_native_bridge/example/.metadata index ea59ad0bc..194e02a67 100644 --- a/quill_native_bridge/quill_native_bridge/example/.metadata +++ b/quill_native_bridge/quill_native_bridge/example/.metadata @@ -15,7 +15,7 @@ migration: - platform: root create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - - platform: windows + - platform: linux create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 diff --git a/quill_native_bridge/quill_native_bridge/example/linux/.gitignore b/quill_native_bridge/quill_native_bridge/example/linux/.gitignore new file mode 100644 index 000000000..d3896c984 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/quill_native_bridge/quill_native_bridge/example/linux/CMakeLists.txt b/quill_native_bridge/quill_native_bridge/example/linux/CMakeLists.txt new file mode 100644 index 000000000..3d327a923 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "dev.flutterquill.example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/quill_native_bridge/quill_native_bridge/example/linux/flutter/CMakeLists.txt b/quill_native_bridge/quill_native_bridge/example/linux/flutter/CMakeLists.txt new file mode 100644 index 000000000..d5bd01648 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.cc b/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..e71a16d23 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.h b/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..e0f0a47bc --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugins.cmake b/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 000000000..2e1de87a7 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/quill_native_bridge/quill_native_bridge/example/linux/main.cc b/quill_native_bridge/quill_native_bridge/example/linux/main.cc new file mode 100644 index 000000000..e7c5c5437 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/quill_native_bridge/quill_native_bridge/example/linux/my_application.cc b/quill_native_bridge/quill_native_bridge/example/linux/my_application.cc new file mode 100644 index 000000000..c0530d422 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/linux/my_application.cc @@ -0,0 +1,124 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/quill_native_bridge/quill_native_bridge/example/linux/my_application.h b/quill_native_bridge/quill_native_bridge/example/linux/my_application.h new file mode 100644 index 000000000..72271d5e4 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ From 57a6b8df9538a20e4c4d98605470ef75123416c4 Mon Sep 17 00:00:00 2001 From: Ellet Date: Tue, 24 Sep 2024 08:18:53 -0700 Subject: [PATCH 45/90] feat(linux): (WIP) highly experimental linux support using xclip --- example/pubspec.yaml | 4 + .../integration_test/integration_test.dart | 8 +- .../example/lib/assets.dart | 4 +- .../quill_native_bridge/example/lib/main.dart | 14 +- .../quill_native_bridge/example/pubspec.yaml | 2 + .../lib/quill_native_bridge.dart | 8 +- .../quill_native_bridge/pubspec.yaml | 7 +- .../pubspec_overrides.yaml | 4 +- .../quill_native_bridge_linux/CHANGELOG.md | 8 + .../quill_native_bridge_linux/LICENSE | 21 ++ .../quill_native_bridge_linux/README.md | 13 ++ .../quill_native_bridge_linux/assets/xclip | Bin 0 -> 30896 bytes .../lib/quill_native_bridge_linux.dart | 204 ++++++++++++++++++ .../lib/src/binary_runner.dart | 66 ++++++ .../lib/src/constants.dart | 5 + .../lib/src/mime_types_constants.dart | 5 + .../lib/src/temp_file_utils.dart | 11 + .../quill_native_bridge_linux/pubspec.yaml | 34 +++ .../pubspec_overrides.yaml | 4 + .../test/validate_package_name_constant.dart | 21 ++ .../test/validate_xclip_test.dart | 21 ++ .../lib/src/platform_feature.dart | 25 ++- 22 files changed, 463 insertions(+), 26 deletions(-) create mode 100644 quill_native_bridge/quill_native_bridge_linux/CHANGELOG.md create mode 100644 quill_native_bridge/quill_native_bridge_linux/LICENSE create mode 100644 quill_native_bridge/quill_native_bridge_linux/README.md create mode 100755 quill_native_bridge/quill_native_bridge_linux/assets/xclip create mode 100644 quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart create mode 100644 quill_native_bridge/quill_native_bridge_linux/lib/src/binary_runner.dart create mode 100644 quill_native_bridge/quill_native_bridge_linux/lib/src/constants.dart create mode 100644 quill_native_bridge/quill_native_bridge_linux/lib/src/mime_types_constants.dart create mode 100644 quill_native_bridge/quill_native_bridge_linux/lib/src/temp_file_utils.dart create mode 100644 quill_native_bridge/quill_native_bridge_linux/pubspec.yaml create mode 100644 quill_native_bridge/quill_native_bridge_linux/pubspec_overrides.yaml create mode 100644 quill_native_bridge/quill_native_bridge_linux/test/validate_package_name_constant.dart create mode 100644 quill_native_bridge/quill_native_bridge_linux/test/validate_xclip_test.dart diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 3dd222969..c963a21d5 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -63,6 +63,10 @@ dependency_overrides: path: ../flutter_quill_test quill_native_bridge: path: ../quill_native_bridge/quill_native_bridge + quill_native_bridge_platform_interface: + path: ../quill_native_bridge/quill_native_bridge_platform_interface + quill_native_bridge_linux: + path: ../quill_native_bridge/quill_native_bridge_linux dev_dependencies: diff --git a/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart b/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart index b5b98fa0e..d46514623 100644 --- a/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart +++ b/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart @@ -13,7 +13,7 @@ void main() { test('copying images to the clipboard should make them accessible', () async { Future verifyImageCopiedToClipboard(String assetPath) async { - final imageBytes = await loadAssetImage(assetPath); + final imageBytes = await loadAssetFile(assetPath); await QuillNativeBridge.copyImageToClipboard(imageBytes); final clipboardImageBytes = await QuillNativeBridge.getClipboardImage(); final pixelMismatchPercentage = @@ -30,8 +30,8 @@ void main() { test( 'copying an image should return the image that was recently copied', () async { - final imageBytes = await loadAssetImage(kFlutterQuillAssetImage); - final imageBytes2 = await loadAssetImage(kQuillJsRichTextEditor); + final imageBytes = await loadAssetFile(kFlutterQuillAssetImage); + final imageBytes2 = await loadAssetFile(kQuillJsRichTextEditor); await QuillNativeBridge.copyImageToClipboard(imageBytes); await QuillNativeBridge.copyImageToClipboard(imageBytes2); @@ -87,7 +87,7 @@ void main() { ); // Image clipboard item - final imageBytes = await loadAssetImage(kFlutterQuillAssetImage); + final imageBytes = await loadAssetFile(kFlutterQuillAssetImage); await QuillNativeBridge.copyImageToClipboard(imageBytes); expect( diff --git a/quill_native_bridge/quill_native_bridge/example/lib/assets.dart b/quill_native_bridge/quill_native_bridge/example/lib/assets.dart index aea78ea8a..8cdd1aeb4 100644 --- a/quill_native_bridge/quill_native_bridge/example/lib/assets.dart +++ b/quill_native_bridge/quill_native_bridge/example/lib/assets.dart @@ -3,6 +3,6 @@ import 'package:flutter/services.dart' show Uint8List, rootBundle; const kFlutterQuillAssetImage = 'assets/flutter-quill.png'; const kQuillJsRichTextEditor = 'assets/quilljs-rich-text-editor.png'; -Future loadAssetImage(String assetPath) async { - return (await rootBundle.load(assetPath)).buffer.asUint8List(); +Future loadAssetFile(String assetFilePath) async { + return (await rootBundle.load(assetFilePath)).buffer.asUint8List(); } diff --git a/quill_native_bridge/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/quill_native_bridge/example/lib/main.dart index f5f2d1286..2aa0c9f7a 100644 --- a/quill_native_bridge/quill_native_bridge/example/lib/main.dart +++ b/quill_native_bridge/quill_native_bridge/example/lib/main.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart' show Clipboard, ClipboardData; import 'package:quill_native_bridge/quill_native_bridge.dart' show QuillNativeBridge, QuillNativeBridgePlatformFeature; @@ -119,7 +118,7 @@ class Buttons extends StatelessWidget { if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported - ? 'Retriving HTML from the Clipboard is currently not supported on the web.' + ? 'Retrieving HTML from the Clipboard is currently not supported on the web.' : 'Getting HTML from the Clipboard is not supported on ${defaultTargetPlatform.name}', ); return; @@ -134,7 +133,6 @@ class Buttons extends StatelessWidget { scaffoldMessenger.showText( 'HTML from the clipboard: $result', ); - await Clipboard.setData(ClipboardData(text: result)); debugPrint('HTML from the clipboard: $result'); break; case QuillNativeBridgePlatformFeature.copyHTMLToClipboard: @@ -167,7 +165,7 @@ class Buttons extends StatelessWidget { ); return; } - final imageBytes = await loadAssetImage(kFlutterQuillAssetImage); + final imageBytes = await loadAssetFile(kFlutterQuillAssetImage); await QuillNativeBridge.copyImageToClipboard(imageBytes); // Not widely supported but some apps copy the image as a text: @@ -191,8 +189,8 @@ class Buttons extends StatelessWidget { if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported - ? 'Retriving an image from the clipboard is currently not supported on web.' - : 'Retriving an image from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', + ? 'Retrieving an image from the clipboard is currently not supported on web.' + : 'Retrieving an image from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', ); return; } @@ -217,8 +215,8 @@ class Buttons extends StatelessWidget { if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported - ? 'Retriving a gif from the clipboard is currently not supported on web.' - : 'Retriving a gif from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', + ? 'Retrieving a gif from the clipboard is currently not supported on web.' + : 'Retrieving a gif from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', ); return; } diff --git a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml index 93ac83c97..27b3ea895 100644 --- a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml @@ -20,6 +20,8 @@ dependency_overrides: path: ../../quill_native_bridge_web quill_native_bridge_windows: path: ../../quill_native_bridge_windows + quill_native_bridge_linux: + path: ../../quill_native_bridge_linux dev_dependencies: integration_test: diff --git a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart index bb964b5d1..48363303f 100644 --- a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart @@ -36,7 +36,7 @@ class QuillNativeBridge { /// Returns `null` if the HTML content is not available or if the user has not granted /// permission for pasting (on some platforms such as iOS). /// - /// Currently only supports **Android**, **iOS**, **macOS**, **Windows** and **Web**. + /// Currently only supports **Android**, **iOS**, **macOS**, **Windows**, **Linux**, and the **Web**. static Future getClipboardHTML() => _platform.getClipboardHTML(); /// Copy the [html] to the clipboard to be pasted on other apps. @@ -45,7 +45,7 @@ class QuillNativeBridge { /// is not supported on the web browser, should fallback to [Clipboard Events](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) /// such as the [copy_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/copy_event). /// - /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. + /// Currently only supports **Android**, **iOS**, **macOS**, **Linux**, and the **Web**. static Future copyHTMLToClipboard(String html) => _platform.copyHTMLToClipboard(html); @@ -60,7 +60,7 @@ class QuillNativeBridge { /// such as the [copy_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/copy_event). /// /// - /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. + /// Currently only supports **Android**, **iOS**, **macOS**, **Linux**, and the **Web**. static Future copyImageToClipboard(Uint8List imageBytes) => _platform.copyImageToClipboard(imageBytes); @@ -70,7 +70,7 @@ class QuillNativeBridge { /// is not supported on the web browser, should fallback to [Clipboard Events](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) /// such as the [paste_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event). /// - /// Currently only supports **Android**, **iOS**, **macOS** and **Web**. + /// Currently only supports **Android**, **iOS**, **macOS**, **Linux**, and the **Web**. static Future getClipboardImage() => _platform.getClipboardImage(); diff --git a/quill_native_bridge/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml index 3d350a246..521eab43e 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec.yaml @@ -11,6 +11,8 @@ platforms: ios: macos: web: + linux: + windows: environment: sdk: '>=3.0.0 <4.0.0' @@ -22,6 +24,7 @@ dependencies: quill_native_bridge_platform_interface: ^0.0.1-dev.0 quill_native_bridge_web: ^0.0.1-dev.1 quill_native_bridge_windows: ^0.0.1-dev.0 + quill_native_bridge_linux: ^0.0.1-dev.0 dev_dependencies: flutter_test: @@ -41,4 +44,6 @@ flutter: web: default_package: quill_native_bridge_web windows: - default_package: quill_native_bridge_windows \ No newline at end of file + default_package: quill_native_bridge_windows + linux: + default_package: quill_native_bridge_linux \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml index 318cf5de8..c0806fdc3 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml @@ -3,4 +3,6 @@ dependency_overrides: quill_native_bridge_web: path: ../quill_native_bridge_web quill_native_bridge_windows: - path: ../quill_native_bridge_windows \ No newline at end of file + path: ../quill_native_bridge_windows + quill_native_bridge_linux: + path: ../quill_native_bridge_linux \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_linux/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_linux/CHANGELOG.md new file mode 100644 index 000000000..167a42b26 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_linux/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +All notable changes to this project will be documented in this file. + + +## 0.0.1-dev.0 + +- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_linux/LICENSE b/quill_native_bridge/quill_native_bridge_linux/LICENSE new file mode 100644 index 000000000..e7ff73e1b --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_linux/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_linux/README.md b/quill_native_bridge/quill_native_bridge_linux/README.md new file mode 100644 index 000000000..cd731524d --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_linux/README.md @@ -0,0 +1,13 @@ +# đŸĒļ Quill Native Bridge + +The Linux implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). + +## ⚙ī¸ Usage + +This package is endorsed, which means you can simply use `quill_native_bridge` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. + +However, if you import this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. + +## 📉 Note on breaking changes + +The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_linux/assets/xclip b/quill_native_bridge/quill_native_bridge_linux/assets/xclip new file mode 100755 index 0000000000000000000000000000000000000000..e1b401e268cb7001e35d2932c3ed1dae9de2672c GIT binary patch literal 30896 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 literal 0 HcmV?d00001 diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart b/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart new file mode 100644 index 000000000..7ac0a2c26 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart @@ -0,0 +1,204 @@ +// This file is referenced by pubspec.yaml. If you plan on moving this file +// Make sure to update pubspec.yaml to the new location. + +import 'dart:convert' show utf8; +import 'dart:io' show Process, File hide exitCode; + +import 'package:flutter/services.dart' show Uint8List; +import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; + +import 'src/binary_runner.dart'; +import 'src/constants.dart'; +import 'src/mime_types_constants.dart'; +import 'src/temp_file_utils.dart'; + +/// A Linux implementation of the [QuillNativeBridgePlatform]. +/// +/// **Highly Experimental** and can be removed. +/// +/// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: +/// +/// ```console +/// Assertion failed: "Platform interfaces must not be implemented with `implements`" +/// ``` +/// +/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) +/// and [QuillNativeBridgePlatform] for more details. +/// ``` +class QuillNativeBridgeLinux extends QuillNativeBridgePlatform { + QuillNativeBridgeLinux._(); + + static void registerWith() { + QuillNativeBridgePlatform.instance = QuillNativeBridgeLinux._(); + } + + // TODO: Improve error handling + + // TODO: The xclipFile should always be removed in finally block, extractBinaryFromAsset() + // should be part of the try-catch + + // TODO: Support wayland https://github.com/bugaevc/wl-clipboard. + // Need to abstract implementation of xclip first. + + // TODO: Might want to improve the description of _hasClipboardItemOfType() + + /// Check if the system clipboard has [mimeType] to paste using [xclip](https://github.com/astrand/xclip). + /// + /// `xclip` doesn't throw an error when retrieving a clipboard item + /// while specifying type using `-t text/html`. + /// + /// Without this check, will return the last copied + /// item even if the last item is an image (as bytes). + /// + /// This only check the type in the clipboard selection. + Future _hasClipboardItemOfType({ + required String mimeType, + required String xclipFilePath, + }) async { + return (await Process.run( + xclipFilePath, ['-selection', 'clipboard', '-t', 'TARGETS', '-o'])) + .stdout + .toString() + .contains(mimeType); + } + + @override + Future getClipboardHTML() async { + final xclipFile = await extractBinaryFromAsset(kXclipAssetFile); + try { + // TODO: Write a test case where copying an image and then retrieving HTML + // should not throw an exception or unexpected behavior. Not required + // since some of the tests will fail if this issue happen. + + // TODO: Should check if the expected type is avalaible before + // avaliable before getting it using: xclip -o -t TARGETS + final hasHtmlInClipboard = await _hasClipboardItemOfType( + mimeType: kHtmlMimeType, + xclipFilePath: xclipFile.path, + ); + if (!hasHtmlInClipboard) { + return null; + } + final result = await Process.run( + xclipFile.path, + ['-selection', 'clipboard', '-o', '-t', kHtmlMimeType], + ); + if (result.exitCode == 0) { + return (result.stdout as String?)?.trim(); + } + final processErrorOutput = result.stderr.toString().trim(); + if (processErrorOutput + .startsWith('Error: target $kHtmlMimeType not available')) { + return null; + } + assert( + false, + 'Error retrieving the HTML to clipboard. Exit code: ${result.exitCode}\nError output: $processErrorOutput', + ); + } finally { + await xclipFile.delete(); + } + return null; + } + + @override + Future copyHTMLToClipboard(String html) async { + final xclipFile = await extractBinaryFromAsset(kXclipAssetFile); + + try { + final process = await Process.start( + xclipFile.path, + [ + '-selection', + 'clipboard', + '-t', + kHtmlMimeType, + ], + ); + process.stdin.writeln(html); + await process.stdin.close(); + final exitCode = await process.exitCode; + if (exitCode != 0) { + final processErrorOutput = + await process.stderr.transform(utf8.decoder).join(); + assert( + false, + 'Error copying the HTML to clipboard. Exit code: $exitCode\nError output: $processErrorOutput', + ); + } + } finally { + await xclipFile.delete(); + } + } + + @override + Future copyImageToClipboard(Uint8List imageBytes) async { + final xclipFile = await extractBinaryFromAsset(kXclipAssetFile); + final tempClipboardImageFileName = + 'tempClipboardImage-${DateTime.now().millisecondsSinceEpoch}.png'; + final tempClipboardImage = + File(generateTempFilePath(tempClipboardImageFileName)); + + try { + await tempClipboardImage.writeAsBytes(imageBytes); + + final process = await Process.start( + xclipFile.path, + [ + '-selection', + 'clipboard', + '-t', + kImagePngMimeType, + '-i', + tempClipboardImage.path, + ], + ); + final exitCode = await process.exitCode; + if (exitCode != 0) { + final errorOutput = await process.stderr.transform(utf8.decoder).join(); + assert( + false, + 'Error copying the image to clipboard. Exit code: $exitCode\nError output: $errorOutput', + ); + } + } finally { + await xclipFile.delete(); + await tempClipboardImage.delete(); + } + } + + @override + Future getClipboardImage() async { + final xclipFile = await extractBinaryFromAsset(kXclipAssetFile); + try { + final hasImagePngInClipboard = await _hasClipboardItemOfType( + mimeType: kImagePngMimeType, + xclipFilePath: xclipFile.path, + ); + if (!hasImagePngInClipboard) { + return null; + } + final result = await Process.run( + xclipFile.path, + ['-selection', 'clipboard', '-t', kImagePngMimeType, '-o'], + // Set stdoutEncoding to null. Expecting raw bytes. + stdoutEncoding: null, + ); + if (result.exitCode == 0) { + return result.stdout as Uint8List?; + } + final processErrorOutput = result.stderr.toString().trim(); + if (processErrorOutput + .startsWith('Error: target $kImagePngMimeType not available')) { + return null; + } + assert( + false, + 'Unknown error while retrieving image from the clipboard. Exit code: ${result.exitCode}. Error output $processErrorOutput', + ); + } finally { + await xclipFile.delete(); + } + return null; + } +} diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/src/binary_runner.dart b/quill_native_bridge/quill_native_bridge_linux/lib/src/binary_runner.dart new file mode 100644 index 000000000..f48e1d401 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_linux/lib/src/binary_runner.dart @@ -0,0 +1,66 @@ +import 'dart:io'; + +import 'package:flutter/services.dart'; + +import 'temp_file_utils.dart'; + +/// Extracts a binary file from the assets to a temporary location +/// to be executed. +/// +/// Copy the binary file located in the assets directory into a temporary +/// directory, and sets the file to be executable. +/// +/// - [assetFilePath] The name of the binary file to extract from assets. +/// +/// Returns: The extracted binary file as [File]. Should be removed when no longer needed. +/// Throws: [FileSystemException] can be thrown with `Text file busy` +/// if [_copyAssetTo] called while the file is executing. +Future extractBinaryFromAsset(String assetFilePath) async { + final extractedBinaryPath = generateTempFilePath(_getFileName(assetFilePath)); + final extractedBinaryFile = File(extractedBinaryPath); + + await _copyAssetTo( + assetFilePath: assetFilePath, + destinationFile: extractedBinaryFile, + ); + await _makeFileExecutable(extractedBinaryPath); + + return extractedBinaryFile; +} + +/// Copies the asset file to a destination directory. +/// +/// - [assetFilePath] The path of the asset file to be copied. +/// - [destinationFile] The target path where the asset file will be copied. +Future _copyAssetTo({ + required String assetFilePath, + required File destinationFile, +}) async { + final assetBytes = + (await rootBundle.load(assetFilePath)).buffer.asUint8List(); + + final parentDirectory = destinationFile.parent; + if (!(await parentDirectory.exists())) { + await parentDirectory.create(recursive: true); + } + + await destinationFile.writeAsBytes(assetBytes); +} + +/// Makes the specified file executable. +/// +/// - [filePath] The path of the file to make executable. +Future _makeFileExecutable(String filePath) async { + assert( + Platform.isLinux, + 'Must be on Linux to add execute permissions with chmod +x.', + ); + await Process.run('chmod', ['+x', filePath]); +} + +/// Extracts the file name from the file path. +/// +/// - [filePath] The full path of the file. +/// +/// Returns: The name of the file extracted from the path. +String _getFileName(String filePath) => filePath.split('/').last; diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/src/constants.dart b/quill_native_bridge/quill_native_bridge_linux/lib/src/constants.dart new file mode 100644 index 000000000..c02998f84 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_linux/lib/src/constants.dart @@ -0,0 +1,5 @@ +/// The same package name in `pubspec.yaml` +const kPackageName = 'quill_native_bridge_linux'; + +/// The asset file path of [xclip](https://github.com/astrand/xclip) binary. +const kXclipAssetFile = 'packages/$kPackageName/assets/xclip'; diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/src/mime_types_constants.dart b/quill_native_bridge/quill_native_bridge_linux/lib/src/mime_types_constants.dart new file mode 100644 index 000000000..57c1fccb7 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_linux/lib/src/mime_types_constants.dart @@ -0,0 +1,5 @@ +// TODO: This is used in web and linux implementation, might extract it in +// common interface + +const String kHtmlMimeType = 'text/html'; +const String kImagePngMimeType = 'image/png'; diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/src/temp_file_utils.dart b/quill_native_bridge/quill_native_bridge_linux/lib/src/temp_file_utils.dart new file mode 100644 index 000000000..0bebdc964 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_linux/lib/src/temp_file_utils.dart @@ -0,0 +1,11 @@ +import 'dart:io' show Directory; + +import 'constants.dart'; + +/// Create a path in the system's temporary directory for a given file name. +/// +/// - [fileName] The name of the file to be stored. +/// +/// Returns: The path where the file will be located. +String generateTempFilePath(String fileName) => + '${Directory.systemTemp.path}/$kPackageName/$fileName'; diff --git a/quill_native_bridge/quill_native_bridge_linux/pubspec.yaml b/quill_native_bridge/quill_native_bridge_linux/pubspec.yaml new file mode 100644 index 000000000..c5f1e8ae6 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_linux/pubspec.yaml @@ -0,0 +1,34 @@ +name: quill_native_bridge_linux +description: "Linux implementation of the quill_native_bridge plugin." +version: 0.0.1-dev.0 +homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_linux +repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_linux +issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ +documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_linux + +environment: + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.0.0' + +dependencies: + flutter: + sdk: flutter + quill_native_bridge_platform_interface: ^0.0.1-dev.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + crypto: ^3.0.5 + yaml: ^3.1.2 + +flutter: + assets: + - assets/xclip + plugin: + implements: quill_native_bridge + platforms: + linux: + pluginClass: none + dartPluginClass: QuillNativeBridgeLinux + fileName: quill_native_bridge_linux.dart \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_linux/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_linux/pubspec_overrides.yaml new file mode 100644 index 000000000..a07f95b27 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_linux/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing +dependency_overrides: + quill_native_bridge_platform_interface: + path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_linux/test/validate_package_name_constant.dart b/quill_native_bridge/quill_native_bridge_linux/test/validate_package_name_constant.dart new file mode 100644 index 000000000..dfeee99f9 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_linux/test/validate_package_name_constant.dart @@ -0,0 +1,21 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:quill_native_bridge_linux/src/constants.dart'; +import 'package:yaml/yaml.dart'; + +void main() { + test('the package name constant should match the one in pubspec.yaml', () { + const pubspecYamlFileName = 'pubspec.yaml'; + final pubspecYamlFile = File(pubspecYamlFileName); + if (!pubspecYamlFile.existsSync()) { + fail( + "The '$pubspecYamlFileName' file doesn't exist. Run the test from the package root directory.", + ); + } + final pubspecYaml = loadYaml(pubspecYamlFile.readAsStringSync()) as YamlMap; + final pubspecYamlPackageName = pubspecYaml['name'] as String?; + + expect(kPackageName, pubspecYamlPackageName); + }); +} diff --git a/quill_native_bridge/quill_native_bridge_linux/test/validate_xclip_test.dart b/quill_native_bridge/quill_native_bridge_linux/test/validate_xclip_test.dart new file mode 100644 index 000000000..5627ba1d8 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_linux/test/validate_xclip_test.dart @@ -0,0 +1,21 @@ +import 'package:crypto/crypto.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quill_native_bridge_linux/src/constants.dart'; + +void main() async { + testWidgets('validate xclip binary file', (tester) async { + // Latest Xclip binary file https://github.com/astrand/xclip + const latestXclipSha512Checksum = + 'f18ad061b8711c7b955edec8e5b203566e9c705da466733d148968a66fbce6db89e540790cc834e2390e4d42d8973b7c0245224e54aadd0133b201b37d3bed79'; + + // Ensure the xclip file is in the assets directory and accessible + // without any runtime issues. + final xclipAssetFileBytes = + (await rootBundle.load(kXclipAssetFile)).buffer.asUint8List(); + final xclipAssetFileSha512Checksum = + sha512.convert(xclipAssetFileBytes).toString(); + + expect(xclipAssetFileSha512Checksum, latestXclipSha512Checksum); + }); +} diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart index 30dff2cf3..bee02385a 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart @@ -51,16 +51,29 @@ enum QuillNativeBridgePlatformFeature { TargetPlatform.iOS, TargetPlatform.macOS, TargetPlatform.windows, + TargetPlatform.linux, }.contains(defaultTargetPlatform), QuillNativeBridgePlatformFeature.copyHTMLToClipboard => kIsWeb || - {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} - .contains(defaultTargetPlatform), + { + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + TargetPlatform.linux, + }.contains(defaultTargetPlatform), QuillNativeBridgePlatformFeature.copyImageToClipboard => kIsWeb || - {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} - .contains(defaultTargetPlatform), + { + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + TargetPlatform.linux, + }.contains(defaultTargetPlatform), QuillNativeBridgePlatformFeature.getClipboardImage => kIsWeb || - {TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS} - .contains(defaultTargetPlatform), + { + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + TargetPlatform.linux, + }.contains(defaultTargetPlatform), QuillNativeBridgePlatformFeature.getClipboardGif => !kIsWeb && {TargetPlatform.android, TargetPlatform.iOS} .contains(defaultTargetPlatform), From 3d6bee6cbbb1c8e359af5952c2948b6ae6ecca8d Mon Sep 17 00:00:00 2001 From: Ellet Date: Tue, 24 Sep 2024 08:24:58 -0700 Subject: [PATCH 46/90] fix(windows): non case sensitive check in stripWin32HtmlDescription() suggested by @AtlasAutocode --- .../quill_native_bridge_windows/lib/src/html_cleaner.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart b/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart index 2bc172338..088751060 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart @@ -53,7 +53,7 @@ String stripWin32HtmlDescription(String html) { for (final line in lines) { // Stop processing when reaching the start of actual HTML content - if (line.startsWith('')) { + if (line.toLowerCase().startsWith('')) { break; } From e1b0eb3b927dbcde5c7e87388cf63c115b3fca2e Mon Sep 17 00:00:00 2001 From: Ellet Date: Tue, 24 Sep 2024 08:29:52 -0700 Subject: [PATCH 47/90] chore(example): unrelated change for windows in Flutter Quill example due to using flutter_inappwebview_windows without running the example on windows (outside of this PR) --- example/windows/flutter/generated_plugin_registrant.cc | 3 +++ example/windows/flutter/generated_plugins.cmake | 1 + 2 files changed, 4 insertions(+) diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc index 084810413..8e3614f6f 100644 --- a/example/windows/flutter/generated_plugin_registrant.cc +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("DesktopDropPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); GalPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("GalPluginCApi")); IrondashEngineContextPluginCApiRegisterWithRegistrar( diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake index f568d1445..a10959609 100644 --- a/example/windows/flutter/generated_plugins.cmake +++ b/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_drop file_selector_windows + flutter_inappwebview_windows gal irondash_engine_context share_plus From 2b7799bc12d605b7e1f11cc46dbedac29f9dbcf0 Mon Sep 17 00:00:00 2001 From: Ellet Date: Thu, 26 Sep 2024 02:30:52 +0300 Subject: [PATCH 48/90] chore(android): cleanup onMethodCall() in QuillNativeBridgePlugin --- .../QuillNativeBridgePlugin.kt | 215 +++--------------- ...andler.kt => ClipboardReadImageHandler.kt} | 4 +- .../clipboard/ClipboardRichTextHandler.kt | 78 +++++++ .../clipboard/ClipboardWriteImageHandler.kt | 128 +++++++++++ 4 files changed, 234 insertions(+), 191 deletions(-) rename quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/{ClipboardImageHandler.kt => ClipboardReadImageHandler.kt} (98%) create mode 100644 quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt create mode 100644 quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt diff --git a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt index e42ad5371..f3d83df4f 100644 --- a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt +++ b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt @@ -1,21 +1,13 @@ package dev.flutterquill.quill_native_bridge -import android.content.ClipData -import android.content.ClipDescription -import android.content.ClipboardManager import android.content.Context -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.ImageDecoder -import android.os.Build -import androidx.core.content.FileProvider -import androidx.core.graphics.decodeBitmap -import dev.flutterquill.quill_native_bridge.clipboard.ClipboardImageHandler +import dev.flutterquill.quill_native_bridge.clipboard.ClipboardReadImageHandler +import dev.flutterquill.quill_native_bridge.clipboard.ClipboardRichTextHandler +import dev.flutterquill.quill_native_bridge.clipboard.ClipboardWriteImageHandler import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import java.io.File class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { private lateinit var channel: MethodChannel @@ -29,183 +21,30 @@ class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { - "getClipboardHTML" -> { - val clipboard = - context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - - if (!clipboard.hasPrimaryClip()) { - result.success(null) - return - } - - val primaryClipData = clipboard.primaryClip - - if (primaryClipData == null || primaryClipData.itemCount == 0) { - result.success(null) - return - } - - val item = primaryClipData.getItemAt(0) - - if (!primaryClipData.description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML)) { - result.success(null) - return - } - - val htmlText = item.htmlText - if (htmlText == null) { - result.error( - "HTML_TEXT_NULL", - "Expected the HTML Text from the Clipboard to be not null", - null - ) - return - } - result.success(htmlText) - } - - "copyHTMLToClipboard" -> { - val html = call.arguments as? String - if (html == null) { - result.error( - "HTML_REQUIRED", - "HTML is required to copy the HTML to the clipboard.", - null - ) - return - } - - try { - val clipboard = - context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newHtmlText("HTML", html, html) - clipboard.setPrimaryClip(clip) - } catch (e: Exception) { - result.error( - "COULD_NOT_COPY_HTML_TO_CLIPBOARD", - "Unknown error while copying the HTML to the clipboard: ${e.message}", - e.toString() - ) - return - } - - result.success(null) - } - - "copyImageToClipboard" -> { - val imageBytes = call.arguments as? ByteArray - if (imageBytes == null) { - result.error( - "IMAGE_BYTES_REQUIRED", - "Image bytes are required to copy the image to the clipboard.", - null, - ) - return - } - - val bitmap: Bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - // Api 29 and above - try { - ImageDecoder.createSource(imageBytes).decodeBitmap { _, _ -> } - } catch (e: Exception) { - result.error( - "INVALID_IMAGE", - "The provided image bytes are invalid, image could not be decoded: ${e.message}", - e.toString(), - ) - return - } - } else { - // Backward compatibility with older versions - val bitmap: Bitmap? = - BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) - if (bitmap == null) { - result.error( - "INVALID_IMAGE", - "The provided image bytes are invalid. Image could not be decoded.", - null - ) - return - } - bitmap - } - - val tempFile = File(context.cacheDir, "temp_clipboard_image.png") - - try { - tempFile.outputStream().use { outputStream -> - val compressedSuccessfully = - bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) - if (!compressedSuccessfully) { - result.error( - "COULD_NOT_COMPRESS_IMAGE", - "Unknown error while compressing the image", - null, - ) - return - } - } - } catch (e: Exception) { - result.error( - "COULD_NOT_SAVE_TEMP_FILE", - "Unknown error while compressing and saving the temporary image file: ${e.message}", - e.toString() - ) - return - } - - val authority = "${context.packageName}.fileprovider" - - val imageUri = try { - FileProvider.getUriForFile( - context, - authority, - tempFile, - ) - } catch (e: IllegalArgumentException) { - result.error( - "ANDROID_MANIFEST_NOT_CONFIGURED", - "You need to configure your AndroidManifest.xml file " + - "to register the provider with the meta-data with authority " + - authority, - e.toString(), - ) - return - } - - try { - val clipboard = - context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newUri(context.contentResolver, "Image", imageUri) - clipboard.setPrimaryClip(clip) - } catch (e: Exception) { - result.error( - "COULD_NOT_COPY_IMAGE_TO_CLIPBOARD", - "Unknown error while copying the image to the clipboard: ${e.message}", - e.toString() - ) - return - } - - result.success(null) - } - - "getClipboardImage" -> { - ClipboardImageHandler.getClipboardImage( - context = context, - // Will convert the image to PNG - imageType = ClipboardImageHandler.ImageType.AnyExceptGif, - result = result, - ) - } - - "getClipboardGif" -> { - ClipboardImageHandler.getClipboardImage( - context = context, - result = result, - imageType = ClipboardImageHandler.ImageType.Gif, - ) - } + "getClipboardHTML" -> ClipboardRichTextHandler.getClipboardHtml( + context = context, result = result, + ) + + "copyHTMLToClipboard" -> ClipboardRichTextHandler.copyHtmlToClipboard( + context = context, call = call, result = result, + ) + + "copyImageToClipboard" -> ClipboardWriteImageHandler.copyImageToClipboard( + context = context, call = call, result = result, + ) + + "getClipboardImage" -> ClipboardReadImageHandler.getClipboardImage( + context = context, + // Will convert the image to PNG + imageType = ClipboardReadImageHandler.ImageType.AnyExceptGif, + result = result, + ) + + "getClipboardGif" -> ClipboardReadImageHandler.getClipboardImage( + context = context, + imageType = ClipboardReadImageHandler.ImageType.Gif, + result = result, + ) else -> result.notImplemented() } diff --git a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardImageHandler.kt b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt similarity index 98% rename from quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardImageHandler.kt rename to quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt index 60f06d6b8..8583b3913 100644 --- a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardImageHandler.kt +++ b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt @@ -13,9 +13,7 @@ import io.flutter.plugin.common.MethodChannel import java.io.ByteArrayOutputStream import java.io.FileNotFoundException -// TODO: Extract what can be extracted outside of ClipboardImageHandler for other method channels - -object ClipboardImageHandler { +object ClipboardReadImageHandler { private const val MIME_TYPE_IMAGE_ALL = "image/*" private const val MIME_TYPE_IMAGE_PNG = "image/png" private const val MIME_TYPE_IMAGE_JPEG = "image/jpeg" diff --git a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt new file mode 100644 index 000000000..e26f0d99a --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt @@ -0,0 +1,78 @@ +package dev.flutterquill.quill_native_bridge.clipboard + +import android.content.ClipData +import android.content.ClipDescription +import android.content.ClipboardManager +import android.content.Context +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel + +object ClipboardRichTextHandler { + fun getClipboardHtml( + context: Context, + result: MethodChannel.Result, + ) { + val clipboard = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + + if (!clipboard.hasPrimaryClip()) { + result.success(null) + return + } + + val primaryClipData = clipboard.primaryClip + + if (primaryClipData == null || primaryClipData.itemCount == 0) { + result.success(null) + return + } + + if (!primaryClipData.description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML)) { + result.success(null) + return + } + + val clipboardItem = primaryClipData.getItemAt(0) + + val htmlText = clipboardItem.htmlText ?: run { + result.error( + "HTML_TEXT_NULL", + "Expected the HTML Text from the Clipboard to be not null", + null, + ) + return + } + result.success(htmlText) + } + + fun copyHtmlToClipboard( + context: Context, + result: MethodChannel.Result, + call: MethodCall + ) { + val html = call.arguments as? String ?: run { + result.error( + "HTML_REQUIRED", + "HTML is required to copy the HTML to the clipboard.", + null, + ) + return + } + + try { + val clipboard = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newHtmlText("HTML", html, html) + clipboard.setPrimaryClip(clip) + } catch (e: Exception) { + result.error( + "COULD_NOT_COPY_HTML_TO_CLIPBOARD", + "Unknown error while copying the HTML to the clipboard: ${e.message}", + e.toString(), + ) + return + } + + result.success(null) + } +} \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt new file mode 100644 index 000000000..afecc4e1a --- /dev/null +++ b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt @@ -0,0 +1,128 @@ +package dev.flutterquill.quill_native_bridge.clipboard + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.ImageDecoder +import android.os.Build +import androidx.core.content.FileProvider +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import java.io.File + +object ClipboardWriteImageHandler { + fun copyImageToClipboard( + context: Context, + call: MethodCall, + result: MethodChannel.Result, + ) { + val imageBytes = call.arguments as? ByteArray ?: run { + result.error( + "IMAGE_BYTES_REQUIRED", + "Image bytes are required to copy the image to the clipboard.", + null, + ) + return + } + + val bitmap: Bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // Api 29 and above (use a newer API) + try { + ImageDecoder.decodeBitmap(ImageDecoder.createSource(imageBytes)) + } catch (e: Exception) { + result.error( + "INVALID_IMAGE", + "The provided image bytes are invalid, image could not be decoded: ${e.message}", + e.toString(), + ) + return + } + } else { + // Backward compatibility with older versions + val bitmap: Bitmap? = + BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + if (bitmap == null) { + result.error( + "INVALID_IMAGE", + "The provided image bytes are invalid. Image could not be decoded.", + null, + ) + return + } + bitmap + } + + val tempImageFile = File(context.cacheDir, "temp_clipboard_image.png") + + try { + tempImageFile.outputStream().use { outputStream -> + val compressedSuccessfully = + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + if (!compressedSuccessfully) { + result.error( + "COULD_NOT_COMPRESS_IMAGE", + "Unknown error while compressing the image", + null, + ) + return + } + } + } catch (e: Exception) { + result.error( + "COULD_NOT_SAVE_TEMP_FILE", + "Unknown error while compressing and saving the temporary image file: ${e.message}", + e.toString(), + ) + return + } + + if (!tempImageFile.exists()) { + result.error( + "TEMP_FILE_NOT_FOUND", + "Recently created temporary file for copying the image to the clipboard is missing.", + null, + ) + return + } + + val authority = "${context.packageName}.fileprovider" + + val imageUri = try { + FileProvider.getUriForFile( + context, + authority, + tempImageFile, + ) + } catch (e: IllegalArgumentException) { + result.error( + "ANDROID_MANIFEST_NOT_CONFIGURED", + "You need to configure your AndroidManifest.xml file " + + "to register the provider with the meta-data with authority " + + authority, + e.toString(), + ) + return + } + + try { + val clipboard = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newUri(context.contentResolver, "Image", imageUri) + clipboard.setPrimaryClip(clip) + + // Don't delete the temporary image file, other apps will be unable to retrieve the image + // tempImageFile.delete() + } catch (e: Exception) { + result.error( + "COULD_NOT_COPY_IMAGE_TO_CLIPBOARD", + "Unknown error while copying the image to the clipboard: ${e.message}", + e.toString(), + ) + return + } + + result.success(null) + } +} \ No newline at end of file From d2cd4e8ea7f534ca6cee6dab2402dbef418cabf9 Mon Sep 17 00:00:00 2001 From: Ellet Date: Thu, 26 Sep 2024 02:55:06 +0300 Subject: [PATCH 49/90] docs(readme): update docs to reflect the changes --- README.md | 23 +++++++++++++++---- flutter_quill_extensions/README.md | 36 ++---------------------------- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 52e2b8af7..d2ff8e521 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ You can join our [Slack Group] for discussion. - [đŸ“Ļ Embed Blocks](#-embed-blocks) - [🔄 Conversion to HTML](#-conversion-to-html) - [📝 Spelling checker](#-spelling-checker) +- [📝 Rich Text Paste](#-rich-text-paste) - [✂ī¸ Shortcut events](#-shortcut-events) - [🌐 Translation](#-translation) - [đŸ§Ē Testing](#-testing) @@ -157,10 +158,9 @@ Create the file `your_project/android/app/src/main/res/xml/file_paths.xml` with ``` > [!NOTE] -> Starting from Flutter Quill `9.4.x`, [super_clipboard](https://pub.dev/packages/super_clipboard) has been moved -> to [FlutterQuill Extensions], to use rich text pasting, support pasting images, and gif files from external apps or -> websites, take a look -> at `flutter_quill_extensions` Readme. +> [super_clipboard](https://pub.dev/packages/super_clipboard) is no longer required +> in recent versions of Flutter Quill in `flutter_quill` or `flutter_quill_extensions`. +> `quill_native_bridge` is a plugin provide clipboard operations functionality for `flutter_quill`. ## 🚀 Usage @@ -346,6 +346,21 @@ The following packages can be used: This feature is currently not implemented and is being planned. Refer to [#2246](https://github.com/singerdmx/flutter-quill/issues/2246) for discussion. +## 📝 Rich Text Paste + +The Rich Text Pasting feature requires platform code to access HTML from +the system clipboard, [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge) +is a plugin to provide this functionality. + +Rich clipboard operations are currently experimental and might be removed in future releases. + + + +> [!IMPORTANT] +> Currently this feature is not supported on the web. +> [Issue #1998](https://github.com/singerdmx/flutter-quill/issues/1998) and [Issue #2220](https://github.com/singerdmx/flutter-quill/issues/2220) + for more details + ## ✂ī¸ Shortcut events We can customize some Shorcut events, using the parameters `characterShortcutEvents` or `spaceShortcutEvents` from `QuillEditorConfigurations` to add more functionality to our editor. diff --git a/flutter_quill_extensions/README.md b/flutter_quill_extensions/README.md index 8a6ab3388..98988ae9d 100644 --- a/flutter_quill_extensions/README.md +++ b/flutter_quill_extensions/README.md @@ -50,11 +50,6 @@ The package uses the following plugins: platform-specific setup. 2. [`image_picker`](https://pub.dev/packages/image_picker) for picking images. See the [Installation](https://pub.dev/packages/image_picker#installation) section. -3. [`super_clipboard`](https://pub.dev/packages/super_clipboard) which needs some setup on Android only, it's used to - support copying images and pasting them into editor, it's also required to support rich text pasting feature on - non-web platforms, Open the [Android Support](https://pub.dev/packages/super_clipboard#android-support) page for - instructions. - The `minSdkVersion` for **Android** is `23` as `super_clipboard` requires it ### Loading Images from the Internet @@ -151,35 +146,8 @@ This works only for non-web platforms. ### 📝 Rich Text Paste Feature -The Rich Text Pasting feature requires native code to access -the `Clipboard` data as HTML, the plugin `super_clipboard` is required on all platforms except Web. - -This package already includes `super_clipboard` and will be used internally in this package, to use it -in `flutter_quill`, call this function before using any of the widgets or functionalities: - -```dart -FlutterQuillExtensions.useSuperClipboardPlugin(); -``` - -`super_clipboard` is a comprehensive plugin that provides many clipboard features for reading and writing rich text, -images and other formats. - -Calling this function will allow `flutter_quill` to use modern rich text features to paste HTML and Markdown, -support for GIF files, and other formats. - -> [!IMPORTANT] -> On web platforms, you can only get the HTML from `Clipboard` on the -> `paste` event, `super_clipboard`, or any plugin is not required. -> The paste feature will not work using the standard paste hotkey logic. -> As such, you will be unable to use the **Rich Text Paste Feature** on a button or in the web app itself. -> So you might want to either display a dialog when pressing the paste button that explains the limitation and shows the -> hotkey they need to press in order to paste or develop an extension for the browser that bypasses this limitation -> similarly to **Google Docs** and provide a link to install the browser extension. -> See [Issue #1998](https://github.com/singerdmx/flutter-quill/issues/1998) for more details. - -> [!NOTE] -> We're still planning on how this should be implemented in -> [Issue #1998](https://github.com/singerdmx/flutter-quill/issues/1998). +The rich text paste feature is now supported directly in `flutter_quill` +as platform code is not bundled with the project. ### đŸ–ŧī¸ Image Assets From 86e235d7ae47dfb86f0db3e67e97c5f0827da734 Mon Sep 17 00:00:00 2001 From: Ellet Date: Thu, 26 Sep 2024 12:54:25 +0300 Subject: [PATCH 50/90] chore: use hasWebSupport instead of hardcoding the check directly in QuillNativeBridgePlatformFeature.isSupported --- .../lib/src/platform_feature.dart | 69 +++++++++---------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart index bee02385a..38e75426a 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart @@ -17,7 +17,7 @@ enum QuillNativeBridgePlatformFeature { /// **Note**: This doesn't check whatever if the web browser support this /// specific feature. /// - /// For example the **Clipboard API** is not supported on **Firefox** + /// For example the **Clipboard API** might not be supported on **Firefox** /// but is supported on the web itself in general, the [hasWebSupport] /// will return `true`. /// @@ -27,9 +27,6 @@ enum QuillNativeBridgePlatformFeature { /// support **Clipboard API**. final bool hasWebSupport; - // Note: the [hasWebSupport] need to be manually updated to be in sync with - // [isSupported] - /// Verify whether a specific feature is supported by the plugin for the [TargetPlatform]. /// /// **Note**: This doesn't check if the platform operating system does support @@ -42,41 +39,41 @@ enum QuillNativeBridgePlatformFeature { /// Always check the docs of the method you're calling to see if there /// are special notes. bool get isSupported { + if (kIsWeb) { + return hasWebSupport; + } return switch (this) { QuillNativeBridgePlatformFeature.isIOSSimulator => !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS, - QuillNativeBridgePlatformFeature.getClipboardHTML => kIsWeb || - { - TargetPlatform.android, - TargetPlatform.iOS, - TargetPlatform.macOS, - TargetPlatform.windows, - TargetPlatform.linux, - }.contains(defaultTargetPlatform), - QuillNativeBridgePlatformFeature.copyHTMLToClipboard => kIsWeb || - { - TargetPlatform.android, - TargetPlatform.iOS, - TargetPlatform.macOS, - TargetPlatform.linux, - }.contains(defaultTargetPlatform), - QuillNativeBridgePlatformFeature.copyImageToClipboard => kIsWeb || - { - TargetPlatform.android, - TargetPlatform.iOS, - TargetPlatform.macOS, - TargetPlatform.linux, - }.contains(defaultTargetPlatform), - QuillNativeBridgePlatformFeature.getClipboardImage => kIsWeb || - { - TargetPlatform.android, - TargetPlatform.iOS, - TargetPlatform.macOS, - TargetPlatform.linux, - }.contains(defaultTargetPlatform), - QuillNativeBridgePlatformFeature.getClipboardGif => !kIsWeb && - {TargetPlatform.android, TargetPlatform.iOS} - .contains(defaultTargetPlatform), + QuillNativeBridgePlatformFeature.getClipboardHTML => { + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + TargetPlatform.windows, + TargetPlatform.linux, + }.contains(defaultTargetPlatform), + QuillNativeBridgePlatformFeature.copyHTMLToClipboard => { + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + TargetPlatform.linux, + }.contains(defaultTargetPlatform), + QuillNativeBridgePlatformFeature.copyImageToClipboard => { + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + TargetPlatform.linux, + }.contains(defaultTargetPlatform), + QuillNativeBridgePlatformFeature.getClipboardImage => { + TargetPlatform.android, + TargetPlatform.iOS, + TargetPlatform.macOS, + TargetPlatform.linux, + }.contains(defaultTargetPlatform), + QuillNativeBridgePlatformFeature.getClipboardGif => { + TargetPlatform.android, + TargetPlatform.iOS + }.contains(defaultTargetPlatform), }; } From bf2e462b1e3a31e7b998cc6f11437aba2d729d7b Mon Sep 17 00:00:00 2001 From: Ellet Date: Thu, 26 Sep 2024 13:31:13 +0300 Subject: [PATCH 51/90] feat: rename MethodChannelQuillNativeBridge.methodChannel to testMethodChannel, check in development mode to ensure that testMethodChannel can be only used in unit tests for non-web platforms --- .../quill_native_bridge_method_channel.dart | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart index 688d89db8..b11045f70 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart @@ -1,14 +1,36 @@ +import 'dart:io' as io show Platform; + import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart' show MethodChannel, PlatformException; import '../quill_native_bridge_platform_interface.dart'; import 'platform_feature.dart'; +const _methodChannel = MethodChannel('quill_native_bridge'); + /// A default [QuillNativeBridgePlatform] implementation backed by a platform /// channel. class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { + /// For tests only @visibleForTesting - final methodChannel = const MethodChannel('quill_native_bridge'); + MethodChannel get testMethodChannel { + assert(() { + if (kIsWeb) { + throw StateError( + 'Could not check if this was a test on web. Method channel should' + 'be only accessed for tests outside of $MethodChannelQuillNativeBridge', + ); + } + if (!io.Platform.environment.containsKey('FLUTTER_TEST')) { + throw StateError( + 'The method channel should be only accessed in tests when used ' + 'outside of $MethodChannelQuillNativeBridge', + ); + } + return true; + }()); + return _methodChannel; + } @override Future isIOSSimulator() async { @@ -21,7 +43,7 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { return true; }()); final isSimulator = - await methodChannel.invokeMethod('isIOSSimulator'); + await _methodChannel.invokeMethod('isIOSSimulator'); assert(() { if (isSimulator == null) { throw FlutterError( @@ -44,7 +66,7 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { return true; }()); final htmlText = - await methodChannel.invokeMethod('getClipboardHTML'); + await _methodChannel.invokeMethod('getClipboardHTML'); return htmlText; } @@ -58,7 +80,7 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { } return true; }()); - await methodChannel.invokeMethod( + await _methodChannel.invokeMethod( 'copyHTMLToClipboard', html, ); @@ -75,7 +97,7 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { return true; }()); try { - await methodChannel.invokeMethod( + await _methodChannel.invokeMethod( 'copyImageToClipboard', imageBytes, ); @@ -108,7 +130,7 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { return true; }()); try { - final imageBytes = await methodChannel.invokeMethod( + final imageBytes = await _methodChannel.invokeMethod( 'getClipboardImage', ); return imageBytes; @@ -134,7 +156,7 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { return true; }()); try { - final gifBytes = await methodChannel.invokeMethod( + final gifBytes = await _methodChannel.invokeMethod( 'getClipboardGif', ); return gifBytes; From d7e3064f240bf576a48ff484f8cf4f854174a12d Mon Sep 17 00:00:00 2001 From: Ellet Date: Thu, 26 Sep 2024 20:00:55 +0300 Subject: [PATCH 52/90] refactor: move android implementation from quill_native_bridge to quill_native_bridge_android, use pigeon for Android --- .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/pubspec.yaml | 2 + .../flutter/generated_plugin_registrant.cc | 3 - .../windows/flutter/generated_plugins.cmake | 1 - .../QuillNativeBridgePlugin.kt | 55 ------- .../quill_native_bridge/example/pubspec.yaml | 2 + .../quill_native_bridge/pubspec.yaml | 4 +- .../pubspec_overrides.yaml | 4 +- .../quill_native_bridge_android/CHANGELOG.md | 7 + .../quill_native_bridge_android/LICENSE | 21 +++ .../quill_native_bridge_android/README.md | 13 ++ .../android/.gitignore | 0 .../android/build.gradle | 0 .../android/settings.gradle | 0 .../android/src/main/AndroidManifest.xml | 0 .../quill_native_bridge/QuillNativeBridge.kt | 28 ++++ .../QuillNativeBridgePlugin.kt | 19 +++ .../clipboard/ClipboardReadImageHandler.kt | 51 +++--- .../clipboard/ClipboardRichTextHandler.kt | 48 ++---- .../clipboard/ClipboardWriteImageHandler.kt | 50 ++---- .../generated/GeneratedMessages.kt | 154 ++++++++++++++++++ .../lib/quill_native_bridge_android.dart | 125 ++++++++++++++ .../lib/src/messages.g.dart | 145 +++++++++++++++++ .../pigeons/messages.dart | 21 +++ .../quill_native_bridge_android/pubspec.yaml | 31 ++++ .../pubspec_overrides.yaml | 4 + ...uill_native_bridge_platform_interface.dart | 1 + .../quill_native_bridge_method_channel.dart | 92 ++--------- 28 files changed, 644 insertions(+), 239 deletions(-) delete mode 100644 quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt create mode 100644 quill_native_bridge/quill_native_bridge_android/CHANGELOG.md create mode 100644 quill_native_bridge/quill_native_bridge_android/LICENSE create mode 100644 quill_native_bridge/quill_native_bridge_android/README.md rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_android}/android/.gitignore (100%) rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_android}/android/build.gradle (100%) rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_android}/android/settings.gradle (100%) rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_android}/android/src/main/AndroidManifest.xml (100%) create mode 100644 quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridge.kt create mode 100644 quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_android}/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt (89%) rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_android}/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt (54%) rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_android}/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt (75%) create mode 100644 quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt create mode 100644 quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart create mode 100644 quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart create mode 100644 quill_native_bridge/quill_native_bridge_android/pigeons/messages.dart create mode 100644 quill_native_bridge/quill_native_bridge_android/pubspec.yaml create mode 100644 quill_native_bridge/quill_native_bridge_android/pubspec_overrides.yaml diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index bf1bbe800..df0d331b7 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -11,6 +11,7 @@ import file_selector_macos import gal import irondash_engine_context import path_provider_foundation +import quill_native_bridge import share_plus import sqflite import super_native_extensions @@ -24,6 +25,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin")) IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index c963a21d5..f436265b5 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -67,6 +67,8 @@ dependency_overrides: path: ../quill_native_bridge/quill_native_bridge_platform_interface quill_native_bridge_linux: path: ../quill_native_bridge/quill_native_bridge_linux + quill_native_bridge_android: + path: ../quill_native_bridge/quill_native_bridge_android dev_dependencies: diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc index 8e3614f6f..084810413 100644 --- a/example/windows/flutter/generated_plugin_registrant.cc +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -20,8 +19,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("DesktopDropPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); - FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); GalPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("GalPluginCApi")); IrondashEngineContextPluginCApiRegisterWithRegistrar( diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake index a10959609..f568d1445 100644 --- a/example/windows/flutter/generated_plugins.cmake +++ b/example/windows/flutter/generated_plugins.cmake @@ -5,7 +5,6 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_drop file_selector_windows - flutter_inappwebview_windows gal irondash_engine_context share_plus diff --git a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt deleted file mode 100644 index f3d83df4f..000000000 --- a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt +++ /dev/null @@ -1,55 +0,0 @@ -package dev.flutterquill.quill_native_bridge - -import android.content.Context -import dev.flutterquill.quill_native_bridge.clipboard.ClipboardReadImageHandler -import dev.flutterquill.quill_native_bridge.clipboard.ClipboardRichTextHandler -import dev.flutterquill.quill_native_bridge.clipboard.ClipboardWriteImageHandler -import io.flutter.embedding.engine.plugins.FlutterPlugin -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.MethodChannel.MethodCallHandler - -class QuillNativeBridgePlugin : FlutterPlugin, MethodCallHandler { - private lateinit var channel: MethodChannel - private lateinit var context: Context - - override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - context = flutterPluginBinding.applicationContext - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "quill_native_bridge") - channel.setMethodCallHandler(this) - } - - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "getClipboardHTML" -> ClipboardRichTextHandler.getClipboardHtml( - context = context, result = result, - ) - - "copyHTMLToClipboard" -> ClipboardRichTextHandler.copyHtmlToClipboard( - context = context, call = call, result = result, - ) - - "copyImageToClipboard" -> ClipboardWriteImageHandler.copyImageToClipboard( - context = context, call = call, result = result, - ) - - "getClipboardImage" -> ClipboardReadImageHandler.getClipboardImage( - context = context, - // Will convert the image to PNG - imageType = ClipboardReadImageHandler.ImageType.AnyExceptGif, - result = result, - ) - - "getClipboardGif" -> ClipboardReadImageHandler.getClipboardImage( - context = context, - imageType = ClipboardReadImageHandler.ImageType.Gif, - result = result, - ) - - else -> result.notImplemented() - } - } - - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) = - channel.setMethodCallHandler(null) -} diff --git a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml index 27b3ea895..30eab9902 100644 --- a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml @@ -16,6 +16,8 @@ dependency_overrides: path: ../ quill_native_bridge_platform_interface: path: ../../quill_native_bridge_platform_interface + quill_native_bridge_android: + path: ../../quill_native_bridge_android quill_native_bridge_web: path: ../../quill_native_bridge_web quill_native_bridge_windows: diff --git a/quill_native_bridge/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml index 521eab43e..aa83ecc20 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec.yaml @@ -21,6 +21,7 @@ environment: dependencies: flutter: sdk: flutter + quill_native_bridge_android: ^0.0.1-dev.0 quill_native_bridge_platform_interface: ^0.0.1-dev.0 quill_native_bridge_web: ^0.0.1-dev.1 quill_native_bridge_windows: ^0.0.1-dev.0 @@ -35,8 +36,7 @@ flutter: plugin: platforms: android: - package: dev.flutterquill.quill_native_bridge - pluginClass: QuillNativeBridgePlugin + default_package: quill_native_bridge_android ios: pluginClass: QuillNativeBridgePlugin macos: diff --git a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml index c0806fdc3..ac19b438c 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml @@ -5,4 +5,6 @@ dependency_overrides: quill_native_bridge_windows: path: ../quill_native_bridge_windows quill_native_bridge_linux: - path: ../quill_native_bridge_linux \ No newline at end of file + path: ../quill_native_bridge_linux + quill_native_bridge_android: + path: ../quill_native_bridge_android \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_android/CHANGELOG.md new file mode 100644 index 000000000..f3225b75a --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_android/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## 0.0.1-dev.0 + +- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/LICENSE b/quill_native_bridge/quill_native_bridge_android/LICENSE new file mode 100644 index 000000000..e7ff73e1b --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_android/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_android/README.md b/quill_native_bridge/quill_native_bridge_android/README.md new file mode 100644 index 000000000..4c8cc6c8d --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_android/README.md @@ -0,0 +1,13 @@ +# đŸĒļ Quill Native Bridge + +The Android implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). + +## ⚙ī¸ Usage + +This package is endorsed, which means you can simply use `quill_native_bridge` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. + +However, if you import this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. + +## 📉 Note on breaking changes + +The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/android/.gitignore b/quill_native_bridge/quill_native_bridge_android/android/.gitignore similarity index 100% rename from quill_native_bridge/quill_native_bridge/android/.gitignore rename to quill_native_bridge/quill_native_bridge_android/android/.gitignore diff --git a/quill_native_bridge/quill_native_bridge/android/build.gradle b/quill_native_bridge/quill_native_bridge_android/android/build.gradle similarity index 100% rename from quill_native_bridge/quill_native_bridge/android/build.gradle rename to quill_native_bridge/quill_native_bridge_android/android/build.gradle diff --git a/quill_native_bridge/quill_native_bridge/android/settings.gradle b/quill_native_bridge/quill_native_bridge_android/android/settings.gradle similarity index 100% rename from quill_native_bridge/quill_native_bridge/android/settings.gradle rename to quill_native_bridge/quill_native_bridge_android/android/settings.gradle diff --git a/quill_native_bridge/quill_native_bridge/android/src/main/AndroidManifest.xml b/quill_native_bridge/quill_native_bridge_android/android/src/main/AndroidManifest.xml similarity index 100% rename from quill_native_bridge/quill_native_bridge/android/src/main/AndroidManifest.xml rename to quill_native_bridge/quill_native_bridge_android/android/src/main/AndroidManifest.xml diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridge.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridge.kt new file mode 100644 index 000000000..0c2f16eda --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridge.kt @@ -0,0 +1,28 @@ +package dev.flutterquill.quill_native_bridge + +import QuillNativeBridgeApi +import android.content.Context +import dev.flutterquill.quill_native_bridge.clipboard.ClipboardReadImageHandler +import dev.flutterquill.quill_native_bridge.clipboard.ClipboardRichTextHandler +import dev.flutterquill.quill_native_bridge.clipboard.ClipboardWriteImageHandler + +class QuillNativeBridge(private val context: Context) : QuillNativeBridgeApi { + override fun getClipboardHtml(): String? = ClipboardRichTextHandler.getClipboardHtml(context) + + override fun copyHtmlToClipboard(html: String) = + ClipboardRichTextHandler.copyHtmlToClipboard(context, html) + + override fun getClipboardImage(): ByteArray? = ClipboardReadImageHandler.getClipboardImage( + context, + // Will convert the image to PNG + imageType = ClipboardReadImageHandler.ImageType.AnyExceptGif, + ) + + override fun copyImageToClipboard(imageBytes: ByteArray) = + ClipboardWriteImageHandler.copyImageToClipboard(context, imageBytes) + + override fun getClipboardGif(): ByteArray? = ClipboardReadImageHandler.getClipboardImage( + context, + imageType = ClipboardReadImageHandler.ImageType.Gif, + ) +} \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt new file mode 100644 index 000000000..9751f6fd3 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt @@ -0,0 +1,19 @@ +package dev.flutterquill.quill_native_bridge + +import QuillNativeBridgeApi +import io.flutter.embedding.engine.plugins.FlutterPlugin + +class QuillNativeBridgePlugin : FlutterPlugin { + private var quillNativeBridge: QuillNativeBridgeApi? = null + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + quillNativeBridge = QuillNativeBridge(binding.applicationContext) + requireNotNull(quillNativeBridge) { "A new instance of $QuillNativeBridgeApi was created that appeared to be null" } + QuillNativeBridgeApi.setUp(binding.binaryMessenger, quillNativeBridge) + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + QuillNativeBridgeApi.setUp(binding.binaryMessenger, quillNativeBridge) + quillNativeBridge = null + } +} diff --git a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt similarity index 89% rename from quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt rename to quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt index 8583b3913..c00b18990 100644 --- a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt @@ -1,5 +1,6 @@ package dev.flutterquill.quill_native_bridge.clipboard +import FlutterError import android.content.ClipData import android.content.ClipboardManager import android.content.Context @@ -9,7 +10,6 @@ import android.graphics.ImageDecoder import android.net.Uri import android.os.Build import androidx.core.graphics.decodeBitmap -import io.flutter.plugin.common.MethodChannel import java.io.ByteArrayOutputStream import java.io.FileNotFoundException @@ -121,25 +121,19 @@ object ClipboardReadImageHandler { fun getClipboardImage( context: Context, imageType: ImageType, - result: MethodChannel.Result, - ) { - val primaryClipData = getPrimaryClip(context) ?: kotlin.run { - result.success(null) - return - } + ): ByteArray? { + val primaryClipData = getPrimaryClip(context) ?: return null + val imageUri = getImageUri( clipData = primaryClipData, imageType = imageType, - ) - if (imageUri == null) { - result.success(null) - return - } + ) ?: return null + try { imageUri.readOrThrow(context) } catch (e: Exception) { when (e) { - is SecurityException -> result.error( + is SecurityException -> throw FlutterError( "FILE_READ_PERMISSION_DENIED", "An image exists on the clipboard, but the app no longer " + "has permission to access it. This may be due to the app's " + @@ -147,26 +141,26 @@ object ClipboardReadImageHandler { e.toString(), ) - is FileNotFoundException -> result.error( + is FileNotFoundException -> throw FlutterError( "FILE_NOT_FOUND", "The image file can't be found, the provided URI could not be opened: ${e.message}", e.toString() ) - else -> result.error( + else -> throw FlutterError( "UNKNOWN_ERROR_READING_FILE", "An unknown occurred while reading the image file URI: ${e.message}", e.toString() ) } - return } - when (imageType) { + val imageBytes = when (imageType) { ImageType.Png, ImageType.Jpeg, - ImageType.AnyExceptGif -> getClipboardImageAsPng(context, result, imageUri) + ImageType.AnyExceptGif -> getClipboardImageAsPng(context, imageUri) - ImageType.Gif -> getClipboardGif(context, result, imageUri) + ImageType.Gif -> getClipboardGif(context, imageUri) } + return imageBytes } /** @@ -175,9 +169,8 @@ object ClipboardReadImageHandler { * */ private fun getClipboardImageAsPng( context: Context, - result: MethodChannel.Result, imageUri: Uri - ) { + ): ByteArray { val bitmap = try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // Api 29 and above (use a newer API) @@ -194,12 +187,11 @@ object ClipboardReadImageHandler { } } } catch (e: Exception) { - result.error( + throw FlutterError( "COULD_NOT_DECODE_IMAGE", "Could not decode bitmap from Uri: ${e.message}", e.toString(), ) - return } val imageBytes = ByteArrayOutputStream().use { outputStream -> @@ -213,33 +205,30 @@ object ClipboardReadImageHandler { outputStream ) if (!compressedSuccessfully) { - result.error( + throw FlutterError( "COULD_NOT_COMPRESS_IMAGE", "Unknown error while compressing the image", null, ) - return } outputStream.toByteArray() } - result.success(imageBytes) + return imageBytes } private fun getClipboardGif( context: Context, - result: MethodChannel.Result, imageUri: Uri - ) { + ): ByteArray { try { val imageBytes = uriToByteArray(context, imageUri) - result.success(imageBytes) + return imageBytes } catch (e: Exception) { - result.error( + throw FlutterError( "COULD_NOT_CONVERT_URI_TO_BYTES", "Could not convert Image URI to ByteArray: ${e.message}", e.toString(), ) - return } } diff --git a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt similarity index 54% rename from quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt rename to quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt index e26f0d99a..9a2917fbe 100644 --- a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt @@ -1,78 +1,54 @@ package dev.flutterquill.quill_native_bridge.clipboard +import FlutterError import android.content.ClipData import android.content.ClipDescription import android.content.ClipboardManager import android.content.Context -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel object ClipboardRichTextHandler { - fun getClipboardHtml( - context: Context, - result: MethodChannel.Result, - ) { + fun getClipboardHtml(context: Context): String? { val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager if (!clipboard.hasPrimaryClip()) { - result.success(null) - return + return null } val primaryClipData = clipboard.primaryClip if (primaryClipData == null || primaryClipData.itemCount == 0) { - result.success(null) - return + return null } if (!primaryClipData.description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML)) { - result.success(null) - return + return null } val clipboardItem = primaryClipData.getItemAt(0) - val htmlText = clipboardItem.htmlText ?: run { - result.error( - "HTML_TEXT_NULL", - "Expected the HTML Text from the Clipboard to be not null", - null, - ) - return - } - result.success(htmlText) + val htmlText = clipboardItem.htmlText ?: throw FlutterError( + "HTML_TEXT_NULL", + "Expected the HTML Text from the Clipboard to be not null" + ) + return htmlText } fun copyHtmlToClipboard( context: Context, - result: MethodChannel.Result, - call: MethodCall + html: String, ) { - val html = call.arguments as? String ?: run { - result.error( - "HTML_REQUIRED", - "HTML is required to copy the HTML to the clipboard.", - null, - ) - return - } - try { val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newHtmlText("HTML", html, html) clipboard.setPrimaryClip(clip) } catch (e: Exception) { - result.error( + throw FlutterError( "COULD_NOT_COPY_HTML_TO_CLIPBOARD", "Unknown error while copying the HTML to the clipboard: ${e.message}", e.toString(), ) - return } - - result.success(null) } } \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt similarity index 75% rename from quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt rename to quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt index afecc4e1a..43e8dc27a 100644 --- a/quill_native_bridge/quill_native_bridge/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt @@ -1,5 +1,6 @@ package dev.flutterquill.quill_native_bridge.clipboard +import FlutterError import android.content.ClipData import android.content.ClipboardManager import android.content.Context @@ -8,49 +9,33 @@ import android.graphics.BitmapFactory import android.graphics.ImageDecoder import android.os.Build import androidx.core.content.FileProvider -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel import java.io.File object ClipboardWriteImageHandler { fun copyImageToClipboard( context: Context, - call: MethodCall, - result: MethodChannel.Result, + imageBytes: ByteArray, ) { - val imageBytes = call.arguments as? ByteArray ?: run { - result.error( - "IMAGE_BYTES_REQUIRED", - "Image bytes are required to copy the image to the clipboard.", - null, - ) - return - } - val bitmap: Bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Api 29 and above (use a newer API) try { ImageDecoder.decodeBitmap(ImageDecoder.createSource(imageBytes)) } catch (e: Exception) { - result.error( + throw FlutterError( "INVALID_IMAGE", "The provided image bytes are invalid, image could not be decoded: ${e.message}", e.toString(), ) - return } } else { // Backward compatibility with older versions - val bitmap: Bitmap? = + val bitmap: Bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) - if (bitmap == null) { - result.error( - "INVALID_IMAGE", - "The provided image bytes are invalid. Image could not be decoded.", - null, - ) - return - } + ?: throw FlutterError( + "INVALID_IMAGE", + "The provided image bytes are invalid. Image could not be decoded.", + null, + ) bitmap } @@ -61,30 +46,27 @@ object ClipboardWriteImageHandler { val compressedSuccessfully = bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) if (!compressedSuccessfully) { - result.error( + throw FlutterError( "COULD_NOT_COMPRESS_IMAGE", "Unknown error while compressing the image", null, ) - return } } } catch (e: Exception) { - result.error( + throw FlutterError( "COULD_NOT_SAVE_TEMP_FILE", "Unknown error while compressing and saving the temporary image file: ${e.message}", e.toString(), ) - return } if (!tempImageFile.exists()) { - result.error( + throw FlutterError( "TEMP_FILE_NOT_FOUND", "Recently created temporary file for copying the image to the clipboard is missing.", null, ) - return } val authority = "${context.packageName}.fileprovider" @@ -96,14 +78,13 @@ object ClipboardWriteImageHandler { tempImageFile, ) } catch (e: IllegalArgumentException) { - result.error( + throw FlutterError( "ANDROID_MANIFEST_NOT_CONFIGURED", "You need to configure your AndroidManifest.xml file " + "to register the provider with the meta-data with authority " + authority, e.toString(), ) - return } try { @@ -115,14 +96,11 @@ object ClipboardWriteImageHandler { // Don't delete the temporary image file, other apps will be unable to retrieve the image // tempImageFile.delete() } catch (e: Exception) { - result.error( + throw FlutterError( "COULD_NOT_COPY_IMAGE_TO_CLIPBOARD", "Unknown error while copying the image to the clipboard: ${e.message}", e.toString(), ) - return } - - result.success(null) } } \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt new file mode 100644 index 000000000..8a6c351cc --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt @@ -0,0 +1,154 @@ +// Autogenerated from Pigeon (v22.4.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer + +private fun wrapResult(result: Any?): List { + return listOf(result) +} + +private fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() +private open class GeneratedMessagesPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return super.readValueOfType(type, buffer) + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + super.writeValue(stream, value) + } +} + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface QuillNativeBridgeApi { + fun getClipboardHtml(): String? + fun copyHtmlToClipboard(html: String) + fun getClipboardImage(): ByteArray? + fun copyImageToClipboard(imageBytes: ByteArray) + fun getClipboardGif(): ByteArray? + + companion object { + /** The codec used by QuillNativeBridgeApi. */ + val codec: MessageCodec by lazy { + GeneratedMessagesPigeonCodec() + } + /** Sets up an instance of `QuillNativeBridgeApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: QuillNativeBridgeApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardHtml$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getClipboardHtml()) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyHtmlToClipboard$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val htmlArg = args[0] as String + val wrapped: List = try { + api.copyHtmlToClipboard(htmlArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardImage$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getClipboardImage()) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyImageToClipboard$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val imageBytesArg = args[0] as ByteArray + val wrapped: List = try { + api.copyImageToClipboard(imageBytesArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardGif$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getClipboardGif()) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart new file mode 100644 index 000000000..08c8c2273 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart @@ -0,0 +1,125 @@ +// This file is referenced by pubspec.yaml. If you plan on moving this file +// Make sure to update pubspec.yaml to the new location. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; + +import 'src/messages.g.dart'; + +/// An implementation of [QuillNativeBridgePlatform] for Android. +/// +/// **Highly Experimental** and can be removed. +/// +/// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: +/// +/// ```console +/// Assertion failed: "Platform interfaces must not be implemented with `implements`" +/// ``` +/// +/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) +/// and [QuillNativeBridgePlatform] for more details. +class QuillNativeBridgeAndroid extends QuillNativeBridgePlatform { + QuillNativeBridgeAndroid._({ + @visibleForTesting QuillNativeBridgeApi? api, + }) : _hostApi = api ?? QuillNativeBridgeApi(); + + final QuillNativeBridgeApi _hostApi; + + /// Registers this class as the default instance of [QuillNativeBridgePlatform]. + static void registerWith() { + assert( + defaultTargetPlatform == TargetPlatform.android && !kIsWeb, + 'QuillNativeBridgeAndroid should be only used for Android.', + ); + QuillNativeBridgePlatform.instance = QuillNativeBridgeAndroid._(); + } + + @override + Future getClipboardHTML() async => _hostApi.getClipboardHtml(); + + @override + Future copyHTMLToClipboard(String html) => + _hostApi.copyHtmlToClipboard(html); + + @override + Future getClipboardImage() async { + try { + return await _hostApi.getClipboardImage(); + } on PlatformException catch (e) { + if (kDebugMode && + (e.code == 'FILE_READ_PERMISSION_DENIED' || + e.code == 'FILE_NOT_FOUND')) { + _printAndroidClipboardImageAccessKnownIssue(e); + return null; + } + rethrow; + } + } + + @override + Future copyImageToClipboard(Uint8List imageBytes) async { + try { + await _hostApi.copyImageToClipboard(imageBytes); + } on PlatformException catch (e) { + if (kDebugMode && e.code == 'ANDROID_MANIFEST_NOT_CONFIGURED') { + debugPrint( + 'It looks like your AndroidManifest.xml is not configured properly ' + 'to support copying images to the clipboard on Android.\n' + "If you're interested in this feature, refer to https://github.com/singerdmx/flutter-quill#-platform-specific-configurations\n" + 'This message will only shown in debug mode.\n' + 'Platform details: ${e.toString()}', + ); + throw AssertionError( + 'Optional AndroidManifest configuration is missing. ' + 'Copying images to the clipboard on Android require modifying `AndroidManifest.xml`. ' + 'A message was shown above this error for more details. This' + 'error will only arise in debug mode.', + ); + } + rethrow; + } + } + + @override + Future getClipboardGif() async { + try { + return await _hostApi.getClipboardGif(); + } on PlatformException catch (e) { + if (kDebugMode && + (e.code == 'FILE_READ_PERMISSION_DENIED' || + e.code == 'FILE_NOT_FOUND')) { + _printAndroidClipboardImageAccessKnownIssue(e); + return null; + } + rethrow; + } + } + + /// Should be only used internally for [getClipboardGif] and [getClipboardImage] + /// for **Android only**. + /// + /// This issue can be caused by `SecurityException` or `FileNotFoundException` + /// from Android side. + /// + /// See [#2243](https://github.com/singerdmx/flutter-quill/issues/2243) for more details. + void _printAndroidClipboardImageAccessKnownIssue(PlatformException e) { + assert( + defaultTargetPlatform == TargetPlatform.android, + '_printAndroidClipboardImageAccessKnownIssue() should be only used for Android.', + ); + assert( + kDebugMode, + '_printAndroidClipboardImageAccessKnownIssue() should be only called in debug mode', + ); + if (kDebugMode) { + debugPrint( + 'Could not retrieve the image from clipbaord as the app no longer have access to the image.\n' + 'This can happen on app restart or lifecycle changes.\n' + 'This is known issue on Android and this message will be only shown in debug mode.\n' + 'Refer to https://github.com/singerdmx/flutter-quill/issues/2243 for discussion.\n' + 'Platform details: ${e.toString()}', + ); + } + } +} diff --git a/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart new file mode 100644 index 000000000..ee81bc1ca --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart @@ -0,0 +1,145 @@ +// Autogenerated from Pigeon (v22.4.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); +} + +class QuillNativeBridgeApi { + /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + QuillNativeBridgeApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future getClipboardHtml() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as String?); + } + } + + Future copyHtmlToClipboard(String html) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([html]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future getClipboardImage() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as Uint8List?); + } + } + + Future copyImageToClipboard(Uint8List imageBytes) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([imageBytes]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future getClipboardGif() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as Uint8List?); + } + } +} diff --git a/quill_native_bridge/quill_native_bridge_android/pigeons/messages.dart b/quill_native_bridge/quill_native_bridge_android/pigeons/messages.dart new file mode 100644 index 000000000..b1a646508 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_android/pigeons/messages.dart @@ -0,0 +1,21 @@ +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + // Using `GeneratedMessages.kt` instead of `Messages.g.kt` to follow + // Kotlin conventions: https://kotlinlang.org/docs/coding-conventions.html#source-file-names + kotlinOut: + 'android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt', + dartPackageName: 'quill_native_bridge_android', +)) +@HostApi() +abstract class QuillNativeBridgeApi { + // HTML + String? getClipboardHtml(); + void copyHtmlToClipboard(String html); + + // Image + Uint8List? getClipboardImage(); + void copyImageToClipboard(Uint8List imageBytes); + Uint8List? getClipboardGif(); +} diff --git a/quill_native_bridge/quill_native_bridge_android/pubspec.yaml b/quill_native_bridge/quill_native_bridge_android/pubspec.yaml new file mode 100644 index 000000000..f0dfbbc62 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_android/pubspec.yaml @@ -0,0 +1,31 @@ +name: quill_native_bridge_android +description: "Android implementation of the quill_native_bridge plugin." +version: 0.0.1-dev.0 +homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_android +repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_android +issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ +documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_android + +environment: + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.0.0' + +dependencies: + flutter: + sdk: flutter + quill_native_bridge_platform_interface: ^0.0.1-dev.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + pigeon: ^22.4.0 + +flutter: + plugin: + implements: quill_native_bridge + platforms: + android: + package: dev.flutterquill.quill_native_bridge + pluginClass: QuillNativeBridgePlugin + dartPluginClass: QuillNativeBridgeAndroid diff --git a/quill_native_bridge/quill_native_bridge_android/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_android/pubspec_overrides.yaml new file mode 100644 index 000000000..a07f95b27 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_android/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing +dependency_overrides: + quill_native_bridge_platform_interface: + path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart index 3a6015dd5..293914a4b 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart @@ -41,6 +41,7 @@ abstract class QuillNativeBridgePlatform extends PlatformInterface { Future isIOSSimulator() => throw UnimplementedError('isIOSSimulator() has not been implemented.'); + // TODO: rename getClipboardHTML() and their related usages to getClipboardHtml() /// Return HTML from the Clipboard. Future getClipboardHTML() => throw UnimplementedError('getClipboardHTML() has not been implemented.'); diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart index b11045f70..848300f45 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart @@ -1,11 +1,17 @@ import 'dart:io' as io show Platform; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart' show MethodChannel, PlatformException; +import 'package:flutter/services.dart' show MethodChannel; import '../quill_native_bridge_platform_interface.dart'; import 'platform_feature.dart'; +// TODO: This was only for iOS, Android, and macOS, now it's no longer needed +// for Android, will be no longer used for iOS and macOS either soon. + +// TODO: Platform-specific check like if this is supported should be removed from +// here as discussed in https://github.com/singerdmx/flutter-quill/pull/2230 + const _methodChannel = MethodChannel('quill_native_bridge'); /// A default [QuillNativeBridgePlatform] implementation backed by a platform @@ -96,25 +102,10 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { } return true; }()); - try { - await _methodChannel.invokeMethod( - 'copyImageToClipboard', - imageBytes, - ); - } on PlatformException catch (e) { - if ((kDebugMode && defaultTargetPlatform == TargetPlatform.android) && - e.code == 'ANDROID_MANIFEST_NOT_CONFIGURED') { - debugPrint( - 'It looks like your AndroidManifest.xml is not configured properly ' - 'to support copying images to the clipboard on Android.\n' - "If you're interested in this feature, refer to https://github.com/singerdmx/flutter-quill#-platform-specific-configurations\n" - 'This message will only shown in debug mode.\n' - 'Platform details: ${e.toString()}', - ); - return; - } - rethrow; - } + await _methodChannel.invokeMethod( + 'copyImageToClipboard', + imageBytes, + ); } // TODO: getClipboardImage() should not return gif files on macOS and iOS, same as Android impl @@ -129,20 +120,10 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { } return true; }()); - try { - final imageBytes = await _methodChannel.invokeMethod( - 'getClipboardImage', - ); - return imageBytes; - } on PlatformException catch (e) { - if ((kDebugMode && defaultTargetPlatform == TargetPlatform.android) && - (e.code == 'FILE_READ_PERMISSION_DENIED' || - e.code == 'FILE_NOT_FOUND')) { - _printAndroidClipboardImageAccessKnownIssue(e); - return null; - } - rethrow; - } + final imageBytes = await _methodChannel.invokeMethod( + 'getClipboardImage', + ); + return imageBytes; } @override @@ -155,46 +136,9 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { } return true; }()); - try { - final gifBytes = await _methodChannel.invokeMethod( - 'getClipboardGif', - ); - return gifBytes; - } on PlatformException catch (e) { - if ((kDebugMode && defaultTargetPlatform == TargetPlatform.android) && - (e.code == 'FILE_READ_PERMISSION_DENIED' || - e.code == 'FILE_NOT_FOUND')) { - _printAndroidClipboardImageAccessKnownIssue(e); - return null; - } - rethrow; - } - } - - /// Should be only used internally for [getClipboardGif] and [getClipboardImage] - /// for **Android only**. - /// - /// This issue can be caused by `SecurityException` or `FileNotFoundException` - /// from Android side. - /// - /// See [#2243](https://github.com/singerdmx/flutter-quill/issues/2243) for more details. - void _printAndroidClipboardImageAccessKnownIssue(PlatformException e) { - assert( - defaultTargetPlatform == TargetPlatform.android, - '_printAndroidClipboardImageAccessKnownIssue() should be only used for Android.', - ); - assert( - kDebugMode, - '_printAndroidClipboardImageAccessKnownIssue() should be only called in debug mode', + final gifBytes = await _methodChannel.invokeMethod( + 'getClipboardGif', ); - if (kDebugMode) { - debugPrint( - 'Could not retrieve the image from clipbaord as the app no longer have access to the image.\n' - 'This can happen on app restart or lifecycle changes.\n' - 'This is known issue on Android and this message will be only shown in debug mode.\n' - 'Refer to https://github.com/singerdmx/flutter-quill/issues/2243 for discussion.\n' - 'Platform details: ${e.toString()}', - ); - } + return gifBytes; } } From fb0f2b801c154dff602f65fe17e6f7790e942eba Mon Sep 17 00:00:00 2001 From: Ellet Date: Thu, 26 Sep 2024 20:11:43 +0300 Subject: [PATCH 53/90] chore: update QuillNativeBridgeAndroid.registerWith() assert message --- .../lib/quill_native_bridge_android.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart index 08c8c2273..1dcf8103a 100644 --- a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart +++ b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart @@ -30,7 +30,7 @@ class QuillNativeBridgeAndroid extends QuillNativeBridgePlatform { static void registerWith() { assert( defaultTargetPlatform == TargetPlatform.android && !kIsWeb, - 'QuillNativeBridgeAndroid should be only used for Android.', + '$QuillNativeBridgeAndroid should be only used for Android.', ); QuillNativeBridgePlatform.instance = QuillNativeBridgeAndroid._(); } From 622e0877543d539367f69740e47a82bc9d53f8ae Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 27 Sep 2024 00:01:08 +0300 Subject: [PATCH 54/90] refactor(apple): move iOS and macOS implementation from quill_native_bridge to quill_native_bridge_ios and quill_native_bridge_macos, use pigeon for iOS and macOS, cleanup the code and update QuillNativeBridge to QuillNativeBridgeImpl on Android --- .../Flutter/GeneratedPluginRegistrant.swift | 2 +- example/pubspec.yaml | 4 + .../example/ios/Podfile.lock | 10 +- .../Flutter/GeneratedPluginRegistrant.swift | 2 +- .../example/macos/Podfile.lock | 10 +- .../quill_native_bridge/example/pubspec.yaml | 4 + .../ios/Classes/QuillNativeBridgePlugin.swift | 57 ------ .../Classes/QuillNativeBridgePlugin.swift | 75 ------- .../quill_native_bridge/pubspec.yaml | 6 +- .../pubspec_overrides.yaml | 6 +- ...tiveBridge.kt => QuillNativeBridgeImpl.kt} | 2 +- .../QuillNativeBridgePlugin.kt | 12 +- .../quill_native_bridge_ios/CHANGELOG.md | 7 + .../quill_native_bridge_ios/LICENSE | 21 ++ .../quill_native_bridge_ios/README.md | 13 ++ .../ios/.gitignore | 0 .../ios/Assets/.gitkeep | 0 .../ios/Classes/Messages.g.swift | 186 ++++++++++++++++++ .../ios/Classes/QuillNativeBridgeImpl.swift | 47 +++++ .../ios/Classes/QuillNativeBridgePlugin.swift | 10 + .../ios/Resources/PrivacyInfo.xcprivacy | 0 .../ios/quill_native_bridge_ios.podspec} | 6 +- .../lib/quill_native_bridge_ios.dart | 56 ++++++ .../lib/src/messages.g.dart | 172 ++++++++++++++++ .../pigeons/messages.dart | 20 ++ .../quill_native_bridge_ios/pubspec.yaml | 30 +++ .../pubspec_overrides.yaml | 4 + .../quill_native_bridge_macos/CHANGELOG.md | 7 + .../quill_native_bridge_macos/LICENSE | 21 ++ .../quill_native_bridge_macos/README.md | 13 ++ .../lib/quill_native_bridge_macos.dart | 58 ++++++ .../lib/src/messages.g.dart | 145 ++++++++++++++ .../macos/Classes/Messages.g.swift | 172 ++++++++++++++++ .../macos/Classes/QuillNativeBridgeImpl.swift | 50 +++++ .../Classes/QuillNativeBridgePlugin.swift | 10 + .../macos/quill_native_bridge_macos.podspec} | 4 +- .../pigeons/messages.dart | 18 ++ .../quill_native_bridge_macos/pubspec.yaml | 30 +++ .../pubspec_overrides.yaml | 4 + ...uill_native_bridge_platform_interface.dart | 2 + 40 files changed, 1137 insertions(+), 159 deletions(-) delete mode 100644 quill_native_bridge/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift delete mode 100644 quill_native_bridge/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift rename quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/{QuillNativeBridge.kt => QuillNativeBridgeImpl.kt} (93%) create mode 100644 quill_native_bridge/quill_native_bridge_ios/CHANGELOG.md create mode 100644 quill_native_bridge/quill_native_bridge_ios/LICENSE create mode 100644 quill_native_bridge/quill_native_bridge_ios/README.md rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_ios}/ios/.gitignore (100%) rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_ios}/ios/Assets/.gitkeep (100%) create mode 100644 quill_native_bridge/quill_native_bridge_ios/ios/Classes/Messages.g.swift create mode 100644 quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgeImpl.swift create mode 100644 quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgePlugin.swift rename quill_native_bridge/{quill_native_bridge => quill_native_bridge_ios}/ios/Resources/PrivacyInfo.xcprivacy (100%) rename quill_native_bridge/{quill_native_bridge/ios/quill_native_bridge.podspec => quill_native_bridge_ios/ios/quill_native_bridge_ios.podspec} (83%) create mode 100644 quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart create mode 100644 quill_native_bridge/quill_native_bridge_ios/lib/src/messages.g.dart create mode 100644 quill_native_bridge/quill_native_bridge_ios/pigeons/messages.dart create mode 100644 quill_native_bridge/quill_native_bridge_ios/pubspec.yaml create mode 100644 quill_native_bridge/quill_native_bridge_ios/pubspec_overrides.yaml create mode 100644 quill_native_bridge/quill_native_bridge_macos/CHANGELOG.md create mode 100644 quill_native_bridge/quill_native_bridge_macos/LICENSE create mode 100644 quill_native_bridge/quill_native_bridge_macos/README.md create mode 100644 quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart create mode 100644 quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart create mode 100644 quill_native_bridge/quill_native_bridge_macos/macos/Classes/Messages.g.swift create mode 100644 quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgeImpl.swift create mode 100644 quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgePlugin.swift rename quill_native_bridge/{quill_native_bridge/macos/quill_native_bridge.podspec => quill_native_bridge_macos/macos/quill_native_bridge_macos.podspec} (84%) create mode 100644 quill_native_bridge/quill_native_bridge_macos/pigeons/messages.dart create mode 100644 quill_native_bridge/quill_native_bridge_macos/pubspec.yaml create mode 100644 quill_native_bridge/quill_native_bridge_macos/pubspec_overrides.yaml diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index df0d331b7..988945165 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -11,7 +11,7 @@ import file_selector_macos import gal import irondash_engine_context import path_provider_foundation -import quill_native_bridge +import quill_native_bridge_macos import share_plus import sqflite import super_native_extensions diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f436265b5..400d58e02 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -69,6 +69,10 @@ dependency_overrides: path: ../quill_native_bridge/quill_native_bridge_linux quill_native_bridge_android: path: ../quill_native_bridge/quill_native_bridge_android + quill_native_bridge_ios: + path: ../quill_native_bridge/quill_native_bridge_ios + quill_native_bridge_macos: + path: ../quill_native_bridge/quill_native_bridge_macos dev_dependencies: diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Podfile.lock b/quill_native_bridge/quill_native_bridge/example/ios/Podfile.lock index 840342f09..c3c4a55f2 100644 --- a/quill_native_bridge/quill_native_bridge/example/ios/Podfile.lock +++ b/quill_native_bridge/quill_native_bridge/example/ios/Podfile.lock @@ -2,26 +2,26 @@ PODS: - Flutter (1.0.0) - integration_test (0.0.1): - Flutter - - quill_native_bridge (0.0.1): + - quill_native_bridge_ios (0.0.1): - Flutter DEPENDENCIES: - Flutter (from `Flutter`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - - quill_native_bridge (from `.symlinks/plugins/quill_native_bridge/ios`) + - quill_native_bridge_ios (from `.symlinks/plugins/quill_native_bridge_ios/ios`) EXTERNAL SOURCES: Flutter: :path: Flutter integration_test: :path: ".symlinks/plugins/integration_test/ios" - quill_native_bridge: - :path: ".symlinks/plugins/quill_native_bridge/ios" + quill_native_bridge_ios: + :path: ".symlinks/plugins/quill_native_bridge_ios/ios" SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 - quill_native_bridge: e5afa7d49c08cf68c52a5e23bc272eba6925c622 + quill_native_bridge_ios: 277bdf5bf0fa5d7a12999556b415a5c63dd76ec4 PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift b/quill_native_bridge/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift index e9e54736c..1acf04d3a 100644 --- a/quill_native_bridge/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/quill_native_bridge/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,7 +5,7 @@ import FlutterMacOS import Foundation -import quill_native_bridge +import quill_native_bridge_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin")) diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Podfile.lock b/quill_native_bridge/quill_native_bridge/example/macos/Podfile.lock index 4aa130df5..7d2149e80 100644 --- a/quill_native_bridge/quill_native_bridge/example/macos/Podfile.lock +++ b/quill_native_bridge/quill_native_bridge/example/macos/Podfile.lock @@ -1,21 +1,21 @@ PODS: - FlutterMacOS (1.0.0) - - quill_native_bridge (0.0.1): + - quill_native_bridge_macos (0.0.1): - FlutterMacOS DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - - quill_native_bridge (from `Flutter/ephemeral/.symlinks/plugins/quill_native_bridge/macos`) + - quill_native_bridge_macos (from `Flutter/ephemeral/.symlinks/plugins/quill_native_bridge_macos/macos`) EXTERNAL SOURCES: FlutterMacOS: :path: Flutter/ephemeral - quill_native_bridge: - :path: Flutter/ephemeral/.symlinks/plugins/quill_native_bridge/macos + quill_native_bridge_macos: + :path: Flutter/ephemeral/.symlinks/plugins/quill_native_bridge_macos/macos SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - quill_native_bridge: 1a3a4bfab7cbe4ed0232a17d8aae201a3ce6d302 + quill_native_bridge_macos: f90985c5269ac7ba84d933605b463d96e5f544fe PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 diff --git a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml index 30eab9902..569249daa 100644 --- a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml @@ -24,6 +24,10 @@ dependency_overrides: path: ../../quill_native_bridge_windows quill_native_bridge_linux: path: ../../quill_native_bridge_linux + quill_native_bridge_ios: + path: ../../quill_native_bridge_ios + quill_native_bridge_macos: + path: ../../quill_native_bridge_macos dev_dependencies: integration_test: diff --git a/quill_native_bridge/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift deleted file mode 100644 index 671ca7b34..000000000 --- a/quill_native_bridge/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Flutter -import UIKit - -public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "quill_native_bridge", binaryMessenger: registrar.messenger()) - let instance = QuillNativeBridgePlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "isIOSSimulator": - #if targetEnvironment(simulator) - result(true) - #else - result(false) - #endif - case "getClipboardHTML": - let pasteboard = UIPasteboard.general - if let htmlData = pasteboard.data(forPasteboardType: "public.html") { - let html = String(data: htmlData, encoding: .utf8) - result(html) - } else { - result(nil) - } - case "copyHTMLToClipboard": - guard let html = call.arguments as? String else { - result(FlutterError(code: "HTML_REQUIRED", message: "HTML is required to copy the HTML to the clipboard.", details: nil)) - return - } - let pasteboard = UIPasteboard.general - pasteboard.setValue(html, forPasteboardType: "public.html") - result(nil) - case "copyImageToClipboard": - if let data = call.arguments as? FlutterStandardTypedData { - if let image = UIImage(data: data.data) { - UIPasteboard.general.image = image - result(nil) - } else { - result(FlutterError(code: "INVALID_IMAGE", message: "Unable to create UIImage from image bytes.", details: nil)) - } - } else { - result(FlutterError(code: "IMAGE_BYTES_REQUIRED", message: "Image bytes are required to copy the image to the clipboard.", details: nil)) - } - case "getClipboardImage": - let image = UIPasteboard.general.image - let data = image?.pngData() - result(data) - case "getClipboardGif": - let data = UIPasteboard.general.data(forPasteboardType: "com.compuserve.gif") - result(data) - default: - result(FlutterMethodNotImplemented) - } - } -} diff --git a/quill_native_bridge/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift deleted file mode 100644 index 7e9a418f1..000000000 --- a/quill_native_bridge/quill_native_bridge/macos/Classes/QuillNativeBridgePlugin.swift +++ /dev/null @@ -1,75 +0,0 @@ -import Cocoa -import FlutterMacOS - -public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "quill_native_bridge", binaryMessenger: registrar.messenger) - let instance = QuillNativeBridgePlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getClipboardHTML": - let pasteboard = NSPasteboard.general - if let htmlData = pasteboard.data(forType: .html) { - let html = String(data: htmlData, encoding: .utf8) - result(html) - } else { - result(nil) - } - case "copyHTMLToClipboard": - guard let html = call.arguments as? String else { - result(FlutterError(code: "HTML_REQUIRED", message: "HTML is required to copy the HTML to the clipboard.", details: nil)) - return - } - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setString(html, forType: .html) - result(nil) - case "copyImageToClipboard": - guard let data = call.arguments as? FlutterStandardTypedData else { - result(FlutterError(code: "IMAGE_BYTES_REQUIRED", message: "Image bytes are required to copy the image to the clipboard.", details: nil)) - return - } - - guard let image = NSImage(data: data.data) else { - result(FlutterError(code: "INVALID_IMAGE", message: "Unable to create NSImage from image bytes.", details: nil)) - return - } - - guard let tiffData = image.tiffRepresentation else { - result(FlutterError(code: "INVALID_IMAGE", message: "Unable to get TIFF representation from NSImage.", details: nil)) - return - } - - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setData(tiffData, forType: .png) - result(nil) - - case "getClipboardImage": - let pasteboard = NSPasteboard.general - - // TODO: This can return null when copying an image from other apps (e.g Telegram, Apple notes), seems to work - // with macOS screenshot and Google Chrome, fix this issue later - guard let image = pasteboard.readObjects(forClasses: [NSImage.self], options: nil)?.first as? NSImage else { - result(nil) - return - } - if let tiffData = image.tiffRepresentation, - let bitmap = NSBitmapImageRep(data: tiffData), - let pngData = bitmap.representation(using: .png, properties: [:]) { - result(pngData) - } else { - result(nil) - } - case "getClipboardGif": - // TODO: Add support for getClipboardGif() on macOS if possible - let availableTypes = NSPasteboard.general.types - result(FlutterError(code: "GIF_UNSUPPORTED", message: "Gif image is not supported on macOS. Available types: \(String(describing: availableTypes))", details: nil)) - default: - result(FlutterMethodNotImplemented) - } - } -} diff --git a/quill_native_bridge/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml index aa83ecc20..ebb673bde 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec.yaml @@ -26,6 +26,8 @@ dependencies: quill_native_bridge_web: ^0.0.1-dev.1 quill_native_bridge_windows: ^0.0.1-dev.0 quill_native_bridge_linux: ^0.0.1-dev.0 + quill_native_bridge_ios: ^0.0.1-dev.0 + quill_native_bridge_macos: ^0.0.1-dev.0 dev_dependencies: flutter_test: @@ -38,9 +40,9 @@ flutter: android: default_package: quill_native_bridge_android ios: - pluginClass: QuillNativeBridgePlugin + default_package: quill_native_bridge_ios macos: - pluginClass: QuillNativeBridgePlugin + default_package: quill_native_bridge_macos web: default_package: quill_native_bridge_web windows: diff --git a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml index ac19b438c..f3758d994 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml @@ -7,4 +7,8 @@ dependency_overrides: quill_native_bridge_linux: path: ../quill_native_bridge_linux quill_native_bridge_android: - path: ../quill_native_bridge_android \ No newline at end of file + path: ../quill_native_bridge_android + quill_native_bridge_ios: + path: ../quill_native_bridge_ios + quill_native_bridge_macos: + path: ../quill_native_bridge_macos \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridge.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgeImpl.kt similarity index 93% rename from quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridge.kt rename to quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgeImpl.kt index 0c2f16eda..bc38548a0 100644 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridge.kt +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgeImpl.kt @@ -6,7 +6,7 @@ import dev.flutterquill.quill_native_bridge.clipboard.ClipboardReadImageHandler import dev.flutterquill.quill_native_bridge.clipboard.ClipboardRichTextHandler import dev.flutterquill.quill_native_bridge.clipboard.ClipboardWriteImageHandler -class QuillNativeBridge(private val context: Context) : QuillNativeBridgeApi { +class QuillNativeBridgeImpl(private val context: Context) : QuillNativeBridgeApi { override fun getClipboardHtml(): String? = ClipboardRichTextHandler.getClipboardHtml(context) override fun copyHtmlToClipboard(html: String) = diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt index 9751f6fd3..4e2f95063 100644 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt @@ -4,16 +4,16 @@ import QuillNativeBridgeApi import io.flutter.embedding.engine.plugins.FlutterPlugin class QuillNativeBridgePlugin : FlutterPlugin { - private var quillNativeBridge: QuillNativeBridgeApi? = null + private var api: QuillNativeBridgeApi? = null override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { - quillNativeBridge = QuillNativeBridge(binding.applicationContext) - requireNotNull(quillNativeBridge) { "A new instance of $QuillNativeBridgeApi was created that appeared to be null" } - QuillNativeBridgeApi.setUp(binding.binaryMessenger, quillNativeBridge) + api = QuillNativeBridgeImpl(binding.applicationContext) + requireNotNull(api) { "A new instance of $QuillNativeBridgeApi was created that appeared to be null" } + QuillNativeBridgeApi.setUp(binding.binaryMessenger, api) } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - QuillNativeBridgeApi.setUp(binding.binaryMessenger, quillNativeBridge) - quillNativeBridge = null + QuillNativeBridgeApi.setUp(binding.binaryMessenger, api) + api = null } } diff --git a/quill_native_bridge/quill_native_bridge_ios/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_ios/CHANGELOG.md new file mode 100644 index 000000000..f3225b75a --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_ios/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## 0.0.1-dev.0 + +- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_ios/LICENSE b/quill_native_bridge/quill_native_bridge_ios/LICENSE new file mode 100644 index 000000000..e7ff73e1b --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_ios/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_ios/README.md b/quill_native_bridge/quill_native_bridge_ios/README.md new file mode 100644 index 000000000..70b079aff --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_ios/README.md @@ -0,0 +1,13 @@ +# đŸĒļ Quill Native Bridge + +The iOS implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). + +## ⚙ī¸ Usage + +This package is endorsed, which means you can simply use `quill_native_bridge` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. + +However, if you import this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. + +## 📉 Note on breaking changes + +The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/ios/.gitignore b/quill_native_bridge/quill_native_bridge_ios/ios/.gitignore similarity index 100% rename from quill_native_bridge/quill_native_bridge/ios/.gitignore rename to quill_native_bridge/quill_native_bridge_ios/ios/.gitignore diff --git a/quill_native_bridge/quill_native_bridge/ios/Assets/.gitkeep b/quill_native_bridge/quill_native_bridge_ios/ios/Assets/.gitkeep similarity index 100% rename from quill_native_bridge/quill_native_bridge/ios/Assets/.gitkeep rename to quill_native_bridge/quill_native_bridge_ios/ios/Assets/.gitkeep diff --git a/quill_native_bridge/quill_native_bridge_ios/ios/Classes/Messages.g.swift b/quill_native_bridge/quill_native_bridge_ios/ios/Classes/Messages.g.swift new file mode 100644 index 000000000..ff9d43de5 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_ios/ios/Classes/Messages.g.swift @@ -0,0 +1,186 @@ +// Autogenerated from Pigeon (v22.4.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Any? + + init(code: String, message: String?, details: Any?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +private class MessagesPigeonCodecReader: FlutterStandardReader { +} + +private class MessagesPigeonCodecWriter: FlutterStandardWriter { +} + +private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return MessagesPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return MessagesPigeonCodecWriter(data: data) + } +} + +class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter()) +} + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol QuillNativeBridgeApi { + func isIosSimulator() throws -> Bool + func getClipboardHtml() throws -> String? + func copyHtmlToClipboard(html: String) throws + func getClipboardImage() throws -> FlutterStandardTypedData? + func copyImageToClipboard(imageBytes: FlutterStandardTypedData) throws + func getClipboardGif() throws -> FlutterStandardTypedData? +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class QuillNativeBridgeApiSetup { + static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared } + /// Sets up an instance of `QuillNativeBridgeApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: QuillNativeBridgeApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let isIosSimulatorChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.isIosSimulator\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + isIosSimulatorChannel.setMessageHandler { _, reply in + do { + let result = try api.isIosSimulator() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + isIosSimulatorChannel.setMessageHandler(nil) + } + let getClipboardHtmlChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardHtml\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getClipboardHtmlChannel.setMessageHandler { _, reply in + do { + let result = try api.getClipboardHtml() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getClipboardHtmlChannel.setMessageHandler(nil) + } + let copyHtmlToClipboardChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.copyHtmlToClipboard\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + copyHtmlToClipboardChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let htmlArg = args[0] as! String + do { + try api.copyHtmlToClipboard(html: htmlArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + copyHtmlToClipboardChannel.setMessageHandler(nil) + } + let getClipboardImageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardImage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getClipboardImageChannel.setMessageHandler { _, reply in + do { + let result = try api.getClipboardImage() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getClipboardImageChannel.setMessageHandler(nil) + } + let copyImageToClipboardChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.copyImageToClipboard\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + copyImageToClipboardChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let imageBytesArg = args[0] as! FlutterStandardTypedData + do { + try api.copyImageToClipboard(imageBytes: imageBytesArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + copyImageToClipboardChannel.setMessageHandler(nil) + } + let getClipboardGifChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardGif\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getClipboardGifChannel.setMessageHandler { _, reply in + do { + let result = try api.getClipboardGif() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getClipboardGifChannel.setMessageHandler(nil) + } + } +} diff --git a/quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgeImpl.swift b/quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgeImpl.swift new file mode 100644 index 000000000..755d5c479 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgeImpl.swift @@ -0,0 +1,47 @@ +import Foundation +import Flutter + +class QuillNativeBridgeImpl: QuillNativeBridgeApi { + func isIosSimulator() throws -> Bool { +#if targetEnvironment(simulator) + return true +#else + return false +#endif + } + + func getClipboardHtml() throws -> String? { + guard let htmlData = UIPasteboard.general.data(forPasteboardType: "public.html") else { + return nil + } + let html = String(data: htmlData, encoding: .utf8) + return html + } + + func copyHtmlToClipboard(html: String) throws { + UIPasteboard.general.setValue(html, forPasteboardType: "public.html") + } + + func copyImageToClipboard(imageBytes: FlutterStandardTypedData) throws { + guard let image = UIImage(data: imageBytes.data) else { + throw PigeonError(code: "INVALID_IMAGE", message: "Unable to create UIImage from image bytes.", details: nil) + } + UIPasteboard.general.image = image + } + + func getClipboardImage() throws -> FlutterStandardTypedData? { + let image = UIPasteboard.general.image + guard let data = image?.pngData() else { + return nil + } + return FlutterStandardTypedData(bytes: data) + } + + func getClipboardGif() throws -> FlutterStandardTypedData? { + guard let data = UIPasteboard.general.data(forPasteboardType: "com.compuserve.gif") else { + return nil + } + return FlutterStandardTypedData(bytes: data) + + } +} diff --git a/quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgePlugin.swift new file mode 100644 index 000000000..d77cff7ef --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgePlugin.swift @@ -0,0 +1,10 @@ +import Flutter +import UIKit + +public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let messenger = registrar.messenger() + let api = QuillNativeBridgeImpl() + QuillNativeBridgeApiSetup.setUp(binaryMessenger: messenger, api: api) + } +} diff --git a/quill_native_bridge/quill_native_bridge/ios/Resources/PrivacyInfo.xcprivacy b/quill_native_bridge/quill_native_bridge_ios/ios/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from quill_native_bridge/quill_native_bridge/ios/Resources/PrivacyInfo.xcprivacy rename to quill_native_bridge/quill_native_bridge_ios/ios/Resources/PrivacyInfo.xcprivacy diff --git a/quill_native_bridge/quill_native_bridge/ios/quill_native_bridge.podspec b/quill_native_bridge/quill_native_bridge_ios/ios/quill_native_bridge_ios.podspec similarity index 83% rename from quill_native_bridge/quill_native_bridge/ios/quill_native_bridge.podspec rename to quill_native_bridge/quill_native_bridge_ios/ios/quill_native_bridge_ios.podspec index 3e59e619e..6690fa6c9 100644 --- a/quill_native_bridge/quill_native_bridge/ios/quill_native_bridge.podspec +++ b/quill_native_bridge/quill_native_bridge_ios/ios/quill_native_bridge_ios.podspec @@ -1,9 +1,9 @@ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint quill_native_bridge.podspec` to validate before publishing. +# Run `pod lib lint quill_native_bridge_ios.podspec` to validate before publishing. # Pod::Spec.new do |s| - s.name = 'quill_native_bridge' + s.name = 'quill_native_bridge_ios' s.version = '0.0.1' s.summary = 'A plugin for flutter_quill' s.description = <<-DESC @@ -25,5 +25,5 @@ An internal plugin for flutter_quill package to access platform-specific APIs. # 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 = {'quill_native_bridge_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + # s.resource_bundles = {'quill_native_bridge_ios_privacy' => ['Resources/PrivacyInfo.xcprivacy']} end diff --git a/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart b/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart new file mode 100644 index 000000000..52e43f8f0 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart @@ -0,0 +1,56 @@ +// This file is referenced by pubspec.yaml. If you plan on moving this file +// Make sure to update pubspec.yaml to the new location. + +import 'package:flutter/foundation.dart'; +import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; + +import 'src/messages.g.dart'; + +/// An implementation of [QuillNativeBridgePlatform] for iOS. +/// +/// **Highly Experimental** and can be removed. +/// +/// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: +/// +/// ```console +/// Assertion failed: "Platform interfaces must not be implemented with `implements`" +/// ``` +/// +/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) +/// and [QuillNativeBridgePlatform] for more details. +class QuillNativeBridgeIos extends QuillNativeBridgePlatform { + QuillNativeBridgeIos._({ + @visibleForTesting QuillNativeBridgeApi? api, + }) : _hostApi = api ?? QuillNativeBridgeApi(); + + final QuillNativeBridgeApi _hostApi; + + /// Registers this class as the default instance of [QuillNativeBridgePlatform]. + static void registerWith() { + assert( + defaultTargetPlatform == TargetPlatform.iOS && !kIsWeb, + '$QuillNativeBridgeIos should be only used for iOS.', + ); + QuillNativeBridgePlatform.instance = QuillNativeBridgeIos._(); + } + + @override + Future isIOSSimulator() => _hostApi.isIosSimulator(); + + @override + Future getClipboardHTML() => _hostApi.getClipboardHtml(); + + @override + Future copyHTMLToClipboard(String html) => + _hostApi.copyHtmlToClipboard(html); + + @override + Future getClipboardImage() => _hostApi.getClipboardImage(); + + @override + Future copyImageToClipboard(Uint8List imageBytes) => + _hostApi.copyImageToClipboard(imageBytes); + + @override + Future getClipboardGif() => _hostApi.getClipboardGif(); +} diff --git a/quill_native_bridge/quill_native_bridge_ios/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_ios/lib/src/messages.g.dart new file mode 100644 index 000000000..b3c027981 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_ios/lib/src/messages.g.dart @@ -0,0 +1,172 @@ +// Autogenerated from Pigeon (v22.4.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); +} + +class QuillNativeBridgeApi { + /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + QuillNativeBridgeApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future isIosSimulator() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.isIosSimulator$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + Future getClipboardHtml() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as String?); + } + } + + Future copyHtmlToClipboard(String html) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([html]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future getClipboardImage() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as Uint8List?); + } + } + + Future copyImageToClipboard(Uint8List imageBytes) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([imageBytes]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future getClipboardGif() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as Uint8List?); + } + } +} diff --git a/quill_native_bridge/quill_native_bridge_ios/pigeons/messages.dart b/quill_native_bridge/quill_native_bridge_ios/pigeons/messages.dart new file mode 100644 index 000000000..3f0df4f1a --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_ios/pigeons/messages.dart @@ -0,0 +1,20 @@ +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + swiftOut: 'ios/Classes/Messages.g.swift', + dartPackageName: 'quill_native_bridge_ios', +)) +@HostApi() +abstract class QuillNativeBridgeApi { + bool isIosSimulator(); + + // HTML + String? getClipboardHtml(); + void copyHtmlToClipboard(String html); + + // Image + Uint8List? getClipboardImage(); + void copyImageToClipboard(Uint8List imageBytes); + Uint8List? getClipboardGif(); +} diff --git a/quill_native_bridge/quill_native_bridge_ios/pubspec.yaml b/quill_native_bridge/quill_native_bridge_ios/pubspec.yaml new file mode 100644 index 000000000..a3b5edd69 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_ios/pubspec.yaml @@ -0,0 +1,30 @@ +name: quill_native_bridge_ios +description: "iOS implementation of the quill_native_bridge plugin." +version: 0.0.1-dev.0 +homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_ios +repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_ios +issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ +documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_ios + +environment: + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.0.0' + +dependencies: + flutter: + sdk: flutter + quill_native_bridge_platform_interface: ^0.0.1-dev.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + pigeon: ^22.4.0 + +flutter: + plugin: + implements: quill_native_bridge + platforms: + ios: + pluginClass: QuillNativeBridgePlugin + dartPluginClass: QuillNativeBridgeIos diff --git a/quill_native_bridge/quill_native_bridge_ios/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_ios/pubspec_overrides.yaml new file mode 100644 index 000000000..a07f95b27 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_ios/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing +dependency_overrides: + quill_native_bridge_platform_interface: + path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_macos/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_macos/CHANGELOG.md new file mode 100644 index 000000000..f3225b75a --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_macos/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## 0.0.1-dev.0 + +- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_macos/LICENSE b/quill_native_bridge/quill_native_bridge_macos/LICENSE new file mode 100644 index 000000000..e7ff73e1b --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_macos/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_macos/README.md b/quill_native_bridge/quill_native_bridge_macos/README.md new file mode 100644 index 000000000..7f17d71af --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_macos/README.md @@ -0,0 +1,13 @@ +# đŸĒļ Quill Native Bridge + +The macOS implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). + +## ⚙ī¸ Usage + +This package is endorsed, which means you can simply use `quill_native_bridge` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. + +However, if you import this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. + +## 📉 Note on breaking changes + +The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart b/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart new file mode 100644 index 000000000..147e598e0 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart @@ -0,0 +1,58 @@ +// This file is referenced by pubspec.yaml. If you plan on moving this file +// Make sure to update pubspec.yaml to the new location. + +import 'package:flutter/foundation.dart'; +import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; + +import 'src/messages.g.dart'; + +/// An implementation of [QuillNativeBridgePlatform] for macOS. +/// +/// **Highly Experimental** and can be removed. +/// +/// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: +/// +/// ```console +/// Assertion failed: "Platform interfaces must not be implemented with `implements`" +/// ``` +/// +/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) +/// and [QuillNativeBridgePlatform] for more details. +class QuillNativeBridgeMacOS extends QuillNativeBridgePlatform { + QuillNativeBridgeMacOS._({ + @visibleForTesting QuillNativeBridgeApi? api, + }) : _hostApi = api ?? QuillNativeBridgeApi(); + + final QuillNativeBridgeApi _hostApi; + + /// Registers this class as the default instance of [QuillNativeBridgePlatform]. + static void registerWith() { + assert( + defaultTargetPlatform == TargetPlatform.macOS && !kIsWeb, + '$QuillNativeBridgeMacOS should be only used for macOS.', + ); + QuillNativeBridgePlatform.instance = QuillNativeBridgeMacOS._(); + } + + @override + Future isIOSSimulator() => throw UnsupportedError( + 'isIOSSimulator() is only supported on iOS.', + ); + + @override + Future getClipboardHTML() => _hostApi.getClipboardHtml(); + + @override + Future copyHTMLToClipboard(String html) => + _hostApi.copyHtmlToClipboard(html); + + @override + Future getClipboardImage() => _hostApi.getClipboardImage(); + + @override + Future copyImageToClipboard(Uint8List imageBytes) => + _hostApi.copyImageToClipboard(imageBytes); + + @override + Future getClipboardGif() => _hostApi.getClipboardGif(); +} diff --git a/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart new file mode 100644 index 000000000..a2ff9d1e2 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart @@ -0,0 +1,145 @@ +// Autogenerated from Pigeon (v22.4.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); +} + +class QuillNativeBridgeApi { + /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + QuillNativeBridgeApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future getClipboardHtml() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as String?); + } + } + + Future copyHtmlToClipboard(String html) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([html]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future getClipboardImage() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as Uint8List?); + } + } + + Future copyImageToClipboard(Uint8List imageBytes) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([imageBytes]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future getClipboardGif() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as Uint8List?); + } + } +} diff --git a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/Messages.g.swift b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/Messages.g.swift new file mode 100644 index 000000000..462914b1c --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/Messages.g.swift @@ -0,0 +1,172 @@ +// Autogenerated from Pigeon (v22.4.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Any? + + init(code: String, message: String?, details: Any?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +private class MessagesPigeonCodecReader: FlutterStandardReader { +} + +private class MessagesPigeonCodecWriter: FlutterStandardWriter { +} + +private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return MessagesPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return MessagesPigeonCodecWriter(data: data) + } +} + +class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter()) +} + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol QuillNativeBridgeApi { + func getClipboardHtml() throws -> String? + func copyHtmlToClipboard(html: String) throws + func getClipboardImage() throws -> FlutterStandardTypedData? + func copyImageToClipboard(imageBytes: FlutterStandardTypedData) throws + func getClipboardGif() throws -> FlutterStandardTypedData? +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class QuillNativeBridgeApiSetup { + static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared } + /// Sets up an instance of `QuillNativeBridgeApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: QuillNativeBridgeApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let getClipboardHtmlChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardHtml\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getClipboardHtmlChannel.setMessageHandler { _, reply in + do { + let result = try api.getClipboardHtml() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getClipboardHtmlChannel.setMessageHandler(nil) + } + let copyHtmlToClipboardChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyHtmlToClipboard\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + copyHtmlToClipboardChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let htmlArg = args[0] as! String + do { + try api.copyHtmlToClipboard(html: htmlArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + copyHtmlToClipboardChannel.setMessageHandler(nil) + } + let getClipboardImageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardImage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getClipboardImageChannel.setMessageHandler { _, reply in + do { + let result = try api.getClipboardImage() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getClipboardImageChannel.setMessageHandler(nil) + } + let copyImageToClipboardChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyImageToClipboard\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + copyImageToClipboardChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let imageBytesArg = args[0] as! FlutterStandardTypedData + do { + try api.copyImageToClipboard(imageBytes: imageBytesArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + copyImageToClipboardChannel.setMessageHandler(nil) + } + let getClipboardGifChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardGif\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getClipboardGifChannel.setMessageHandler { _, reply in + do { + let result = try api.getClipboardGif() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getClipboardGifChannel.setMessageHandler(nil) + } + } +} diff --git a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgeImpl.swift b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgeImpl.swift new file mode 100644 index 000000000..3b9fad280 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgeImpl.swift @@ -0,0 +1,50 @@ +import Foundation +import FlutterMacOS + +class QuillNativeBridgeImpl: QuillNativeBridgeApi { + func getClipboardHtml() throws -> String? { + guard let htmlData = NSPasteboard.general.data(forType: .html) else { + return nil + } + let html = String(data: htmlData, encoding: .utf8) + return html + } + + func copyHtmlToClipboard(html: String) throws { + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString(html, forType: .html) + } + + func getClipboardImage() throws -> FlutterStandardTypedData? { + // TODO: This can return null when copying an image from some apps (e.g Telegram, Apple notes), seems to work with macOS screenshot and Google Chrome, attemp to fix it later + guard let image = NSPasteboard.general.readObjects(forClasses: [NSImage.self], options: nil)?.first as? NSImage else { + return nil + } + guard let tiffData = image.tiffRepresentation, + let bitmap = NSBitmapImageRep(data: tiffData), + let pngData = bitmap.representation(using: .png, properties: [:]) else { + return nil + } + return FlutterStandardTypedData(bytes: pngData) + } + + func copyImageToClipboard(imageBytes: FlutterStandardTypedData) throws { + guard let image = NSImage(data: imageBytes.data) else { + throw PigeonError(code: "INVALID_IMAGE", message: "Unable to create NSImage from image bytes.", details: nil) + } + + guard let tiffData = image.tiffRepresentation else { + throw PigeonError(code: "INVALID_IMAGE", message: "Unable to get TIFF representation from NSImage.", details: nil) + } + + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setData(tiffData, forType: .png) + } + + func getClipboardGif() throws -> FlutterStandardTypedData? { + let availableTypes = NSPasteboard.general.types + throw PigeonError(code: "GIF_UNSUPPORTED", message: "Gif image is not supported on macOS. Available types: \(String(describing: availableTypes))", details: nil) + } +} diff --git a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgePlugin.swift new file mode 100644 index 000000000..7aeb6a320 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgePlugin.swift @@ -0,0 +1,10 @@ +import Cocoa +import FlutterMacOS + +public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let messenger = registrar.messenger + let api = QuillNativeBridgeImpl() + QuillNativeBridgeApiSetup.setUp(binaryMessenger: messenger, api: api) + } +} diff --git a/quill_native_bridge/quill_native_bridge/macos/quill_native_bridge.podspec b/quill_native_bridge/quill_native_bridge_macos/macos/quill_native_bridge_macos.podspec similarity index 84% rename from quill_native_bridge/quill_native_bridge/macos/quill_native_bridge.podspec rename to quill_native_bridge/quill_native_bridge_macos/macos/quill_native_bridge_macos.podspec index 75681a6ce..4df29742a 100644 --- a/quill_native_bridge/quill_native_bridge/macos/quill_native_bridge.podspec +++ b/quill_native_bridge/quill_native_bridge_macos/macos/quill_native_bridge_macos.podspec @@ -1,9 +1,9 @@ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint quill_native_bridge.podspec` to validate before publishing. +# Run `pod lib lint quill_native_bridge_macos.podspec` to validate before publishing. # Pod::Spec.new do |s| - s.name = 'quill_native_bridge' + s.name = 'quill_native_bridge_macos' s.version = '0.0.1' s.summary = 'A plugin for flutter_quill' s.description = <<-DESC diff --git a/quill_native_bridge/quill_native_bridge_macos/pigeons/messages.dart b/quill_native_bridge/quill_native_bridge_macos/pigeons/messages.dart new file mode 100644 index 000000000..1a01a2176 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_macos/pigeons/messages.dart @@ -0,0 +1,18 @@ +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + swiftOut: 'macos/Classes/Messages.g.swift', + dartPackageName: 'quill_native_bridge_macos', +)) +@HostApi() +abstract class QuillNativeBridgeApi { + // HTML + String? getClipboardHtml(); + void copyHtmlToClipboard(String html); + + // Image + Uint8List? getClipboardImage(); + void copyImageToClipboard(Uint8List imageBytes); + Uint8List? getClipboardGif(); +} diff --git a/quill_native_bridge/quill_native_bridge_macos/pubspec.yaml b/quill_native_bridge/quill_native_bridge_macos/pubspec.yaml new file mode 100644 index 000000000..d1078ca07 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_macos/pubspec.yaml @@ -0,0 +1,30 @@ +name: quill_native_bridge_macos +description: "macOS implementation of the quill_native_bridge plugin." +version: 0.0.1-dev.0 +homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_macos +repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_macos +issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ +documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_macos + +environment: + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.0.0' + +dependencies: + flutter: + sdk: flutter + quill_native_bridge_platform_interface: ^0.0.1-dev.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + pigeon: ^22.4.0 + +flutter: + plugin: + implements: quill_native_bridge + platforms: + macos: + pluginClass: QuillNativeBridgePlugin + dartPluginClass: QuillNativeBridgeMacOS diff --git a/quill_native_bridge/quill_native_bridge_macos/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_macos/pubspec_overrides.yaml new file mode 100644 index 000000000..a07f95b27 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_macos/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing +dependency_overrides: + quill_native_bridge_platform_interface: + path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart index 293914a4b..d4cf5f90a 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart @@ -37,6 +37,8 @@ abstract class QuillNativeBridgePlatform extends PlatformInterface { _instance = instance; } + // TODO: rename isIOSSimulator() and their related usages to isIosSimulator() + /// Check if the app is running on [iOS Simulator](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device). Future isIOSSimulator() => throw UnimplementedError('isIOSSimulator() has not been implemented.'); From 17845c8d434f7b3b1183540926700e6449a3a718 Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 27 Sep 2024 00:14:31 +0300 Subject: [PATCH 55/90] chore: add quill_native_bridge_platform_interface in pubspec_overides.yaml of quill_native_bridge --- .../quill_native_bridge/pubspec_overrides.yaml | 4 +++- .../lib/quill_native_bridge_platform_interface.dart | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml index f3758d994..27bfdc2a6 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml @@ -11,4 +11,6 @@ dependency_overrides: quill_native_bridge_ios: path: ../quill_native_bridge_ios quill_native_bridge_macos: - path: ../quill_native_bridge_macos \ No newline at end of file + path: ../quill_native_bridge_macos + quill_native_bridge_platform_interface: + path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart index d4cf5f90a..293914a4b 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart @@ -37,8 +37,6 @@ abstract class QuillNativeBridgePlatform extends PlatformInterface { _instance = instance; } - // TODO: rename isIOSSimulator() and their related usages to isIosSimulator() - /// Check if the app is running on [iOS Simulator](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device). Future isIOSSimulator() => throw UnimplementedError('isIOSSimulator() has not been implemented.'); From d3e9357a15eae89d72e90c6323be9bf60fe73df2 Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 27 Sep 2024 00:26:05 +0300 Subject: [PATCH 56/90] refactor: rename copyHTMLToClipboard() and getClipboardHTML() to copyHtmlToClipboard() and getClipboardHtml() --- .../clipboard/default_clipboard_service.dart | 4 +-- pubspec_overrides.yaml | 4 ++- .../integration_test/integration_test.dart | 36 +++++++++---------- .../quill_native_bridge/example/lib/main.dart | 12 +++---- .../lib/quill_native_bridge.dart | 6 ++-- .../lib/quill_native_bridge_android.dart | 4 +-- .../lib/quill_native_bridge_ios.dart | 4 +-- .../lib/quill_native_bridge_linux.dart | 4 +-- .../lib/quill_native_bridge_macos.dart | 4 +-- ...uill_native_bridge_platform_interface.dart | 9 +++-- .../lib/src/platform_feature.dart | 8 ++--- .../quill_native_bridge_method_channel.dart | 16 ++++----- .../test/quill_native_bridge_test.dart | 12 +++---- .../lib/quill_native_bridge_web.dart | 4 +-- .../lib/quill_native_bridge_windows.dart | 2 +- 15 files changed, 65 insertions(+), 64 deletions(-) diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart index cdef6a617..00875a1f9 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart @@ -11,10 +11,10 @@ import 'clipboard_service.dart'; class DefaultClipboardService extends ClipboardService { @override Future getHtmlText() async { - if (QuillNativeBridgePlatformFeature.getClipboardHTML.isUnsupported) { + if (QuillNativeBridgePlatformFeature.getClipboardHtml.isUnsupported) { return null; } - return await QuillNativeBridge.getClipboardHTML(); + return await QuillNativeBridge.getClipboardHtml(); } @override diff --git a/pubspec_overrides.yaml b/pubspec_overrides.yaml index 0614c0c1c..02edfad58 100644 --- a/pubspec_overrides.yaml +++ b/pubspec_overrides.yaml @@ -1,4 +1,6 @@ # TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing dependency_overrides: quill_native_bridge: - path: ./quill_native_bridge/quill_native_bridge \ No newline at end of file + path: ./quill_native_bridge/quill_native_bridge + quill_native_bridge_platform_interface: + path: ./quill_native_bridge/quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart b/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart index d46514623..c60672445 100644 --- a/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart +++ b/quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart @@ -48,13 +48,13 @@ void main() { ); }); - group('getClipboardHTML and copyHTMLToClipbaord', () { + group('getClipboardHtml and copyHtmlToClipbaord', () { test('copying HTML to the clipboard should make it accessible', () async { const htmlToCopy = '

Test Document

This is a sample paragraph with a link and some red text.

  • Item 1
  • Item 2
  • Item 3
Footer content here
'; - await QuillNativeBridge.copyHTMLToClipboard(htmlToCopy); - final clipboardHTML = await QuillNativeBridge.getClipboardHTML(); - expect(htmlToCopy, clipboardHTML); + await QuillNativeBridge.copyHtmlToClipboard(htmlToCopy); + final clipboardHtml = await QuillNativeBridge.getClipboardHtml(); + expect(htmlToCopy, clipboardHtml); }); test('copying HTML should return the HTML that was recently copied', @@ -62,16 +62,16 @@ void main() { const html1 = '
HTML
'; const html2 = '
HTML Div
'; - await QuillNativeBridge.copyHTMLToClipboard(html1); - await QuillNativeBridge.copyHTMLToClipboard(html2); + await QuillNativeBridge.copyHtmlToClipboard(html1); + await QuillNativeBridge.copyHtmlToClipboard(html2); - final clipboardHTML = await QuillNativeBridge.getClipboardHTML(); - expect(clipboardHTML, isNot(html1)); - expect(clipboardHTML, html2); + final clipboardHtml = await QuillNativeBridge.getClipboardHtml(); + expect(clipboardHtml, isNot(html1)); + expect(clipboardHtml, html2); }); // TODO: See if there is a need for writing a similar test for getClipboardImage test( - 'getClipboardHTML should return the HTML content after copying HTML, ' + 'getClipboardHtml should return the HTML content after copying HTML, ' 'and should no longer return HTML once an image (or any non-HTML item) ' 'has been copied to the clipboard after that.', () async { @@ -79,10 +79,10 @@ void main() { // Copy HTML to clipboard before copying an image - await QuillNativeBridge.copyHTMLToClipboard(html); + await QuillNativeBridge.copyHtmlToClipboard(html); expect( - await QuillNativeBridge.getClipboardHTML(), + await QuillNativeBridge.getClipboardHtml(), html, ); @@ -91,16 +91,16 @@ void main() { await QuillNativeBridge.copyImageToClipboard(imageBytes); expect( - await QuillNativeBridge.getClipboardHTML(), + await QuillNativeBridge.getClipboardHtml(), null, ); // Copy HTML to clipboard before copying plain text - await QuillNativeBridge.copyHTMLToClipboard(html); + await QuillNativeBridge.copyHtmlToClipboard(html); expect( - await QuillNativeBridge.getClipboardHTML(), + await QuillNativeBridge.getClipboardHtml(), html, ); @@ -116,7 +116,7 @@ void main() { ); expect( - await QuillNativeBridge.getClipboardHTML(), + await QuillNativeBridge.getClipboardHtml(), null, ); }, @@ -129,8 +129,8 @@ void main() { () async { const exampleHtml = '
HTML Div
'; - await QuillNativeBridge.copyHTMLToClipboard(exampleHtml); - final clipboardHtml = await QuillNativeBridge.getClipboardHTML(); + await QuillNativeBridge.copyHtmlToClipboard(exampleHtml); + final clipboardHtml = await QuillNativeBridge.getClipboardHtml(); if (clipboardHtml == null) { fail( diff --git a/quill_native_bridge/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/quill_native_bridge/example/lib/main.dart index 2aa0c9f7a..35d2d8585 100644 --- a/quill_native_bridge/quill_native_bridge/example/lib/main.dart +++ b/quill_native_bridge/quill_native_bridge/example/lib/main.dart @@ -50,7 +50,7 @@ class Buttons extends StatelessWidget { ), ElevatedButton.icon( onPressed: () => _onButtonClick( - QuillNativeBridgePlatformFeature.getClipboardHTML, + QuillNativeBridgePlatformFeature.getClipboardHtml, context: context, ), label: const Text('Get HTML from Clipboard'), @@ -58,7 +58,7 @@ class Buttons extends StatelessWidget { ), ElevatedButton.icon( onPressed: () => _onButtonClick( - QuillNativeBridgePlatformFeature.copyHTMLToClipboard, + QuillNativeBridgePlatformFeature.copyHtmlToClipboard, context: context, ), label: const Text('Copy HTML to Clipboard'), @@ -114,7 +114,7 @@ class Buttons extends StatelessWidget { ? "You're running the app on iOS simulator" : "You're running the app on real iOS device."); break; - case QuillNativeBridgePlatformFeature.getClipboardHTML: + case QuillNativeBridgePlatformFeature.getClipboardHtml: if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported @@ -123,7 +123,7 @@ class Buttons extends StatelessWidget { ); return; } - final result = await QuillNativeBridge.getClipboardHTML(); + final result = await QuillNativeBridge.getClipboardHtml(); if (result == null) { scaffoldMessenger.showText( 'The HTML is not available on the clipboard.', @@ -135,7 +135,7 @@ class Buttons extends StatelessWidget { ); debugPrint('HTML from the clipboard: $result'); break; - case QuillNativeBridgePlatformFeature.copyHTMLToClipboard: + case QuillNativeBridgePlatformFeature.copyHtmlToClipboard: if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported @@ -151,7 +151,7 @@ class Buttons extends StatelessWidget { Red text Highlighted text '''; - await QuillNativeBridge.copyHTMLToClipboard(html); + await QuillNativeBridge.copyHtmlToClipboard(html); scaffoldMessenger.showText( 'HTML copied to the clipboard: $html', ); diff --git a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart index 48363303f..4165f179e 100644 --- a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart @@ -37,7 +37,7 @@ class QuillNativeBridge { /// permission for pasting (on some platforms such as iOS). /// /// Currently only supports **Android**, **iOS**, **macOS**, **Windows**, **Linux**, and the **Web**. - static Future getClipboardHTML() => _platform.getClipboardHTML(); + static Future getClipboardHtml() => _platform.getClipboardHtml(); /// Copy the [html] to the clipboard to be pasted on other apps. /// @@ -46,8 +46,8 @@ class QuillNativeBridge { /// such as the [copy_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/copy_event). /// /// Currently only supports **Android**, **iOS**, **macOS**, **Linux**, and the **Web**. - static Future copyHTMLToClipboard(String html) => - _platform.copyHTMLToClipboard(html); + static Future copyHtmlToClipboard(String html) => + _platform.copyHtmlToClipboard(html); /// Copy the [imageBytes] to Clipboard to be pasted on other apps. /// diff --git a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart index 1dcf8103a..c6472c09d 100644 --- a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart +++ b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart @@ -36,10 +36,10 @@ class QuillNativeBridgeAndroid extends QuillNativeBridgePlatform { } @override - Future getClipboardHTML() async => _hostApi.getClipboardHtml(); + Future getClipboardHtml() async => _hostApi.getClipboardHtml(); @override - Future copyHTMLToClipboard(String html) => + Future copyHtmlToClipboard(String html) => _hostApi.copyHtmlToClipboard(html); @override diff --git a/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart b/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart index 52e43f8f0..d6f34c4ce 100644 --- a/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart +++ b/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart @@ -38,10 +38,10 @@ class QuillNativeBridgeIos extends QuillNativeBridgePlatform { Future isIOSSimulator() => _hostApi.isIosSimulator(); @override - Future getClipboardHTML() => _hostApi.getClipboardHtml(); + Future getClipboardHtml() => _hostApi.getClipboardHtml(); @override - Future copyHTMLToClipboard(String html) => + Future copyHtmlToClipboard(String html) => _hostApi.copyHtmlToClipboard(html); @override diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart b/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart index 7ac0a2c26..a07d35ca1 100644 --- a/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart +++ b/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart @@ -63,7 +63,7 @@ class QuillNativeBridgeLinux extends QuillNativeBridgePlatform { } @override - Future getClipboardHTML() async { + Future getClipboardHtml() async { final xclipFile = await extractBinaryFromAsset(kXclipAssetFile); try { // TODO: Write a test case where copying an image and then retrieving HTML @@ -102,7 +102,7 @@ class QuillNativeBridgeLinux extends QuillNativeBridgePlatform { } @override - Future copyHTMLToClipboard(String html) async { + Future copyHtmlToClipboard(String html) async { final xclipFile = await extractBinaryFromAsset(kXclipAssetFile); try { diff --git a/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart b/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart index 147e598e0..955092ba1 100644 --- a/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart +++ b/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart @@ -40,10 +40,10 @@ class QuillNativeBridgeMacOS extends QuillNativeBridgePlatform { ); @override - Future getClipboardHTML() => _hostApi.getClipboardHtml(); + Future getClipboardHtml() => _hostApi.getClipboardHtml(); @override - Future copyHTMLToClipboard(String html) => + Future copyHtmlToClipboard(String html) => _hostApi.copyHtmlToClipboard(html); @override diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart index 293914a4b..b132d32d1 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart @@ -41,14 +41,13 @@ abstract class QuillNativeBridgePlatform extends PlatformInterface { Future isIOSSimulator() => throw UnimplementedError('isIOSSimulator() has not been implemented.'); - // TODO: rename getClipboardHTML() and their related usages to getClipboardHtml() /// Return HTML from the Clipboard. - Future getClipboardHTML() => - throw UnimplementedError('getClipboardHTML() has not been implemented.'); + Future getClipboardHtml() => + throw UnimplementedError('getClipboardHtml() has not been implemented.'); /// Copy the [html] to the clipboard to be pasted on other apps. - Future copyHTMLToClipboard(String html) => throw UnimplementedError( - 'copyHTMLToClipboard() has not been implemented.'); + Future copyHtmlToClipboard(String html) => throw UnimplementedError( + 'copyHtmlToClipboard() has not been implemented.'); /// Copy the [imageBytes] to Clipboard to be pasted on other apps. Future copyImageToClipboard(Uint8List imageBytes) => diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart index 38e75426a..2395bdec4 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart @@ -4,8 +4,8 @@ import 'package:flutter/foundation.dart' /// The features/methods provided by the plugin enum QuillNativeBridgePlatformFeature { isIOSSimulator(hasWebSupport: false), - getClipboardHTML(hasWebSupport: true), - copyHTMLToClipboard(hasWebSupport: true), + getClipboardHtml(hasWebSupport: true), + copyHtmlToClipboard(hasWebSupport: true), copyImageToClipboard(hasWebSupport: true), getClipboardImage(hasWebSupport: true), getClipboardGif(hasWebSupport: false); @@ -45,14 +45,14 @@ enum QuillNativeBridgePlatformFeature { return switch (this) { QuillNativeBridgePlatformFeature.isIOSSimulator => !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS, - QuillNativeBridgePlatformFeature.getClipboardHTML => { + QuillNativeBridgePlatformFeature.getClipboardHtml => { TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS, TargetPlatform.windows, TargetPlatform.linux, }.contains(defaultTargetPlatform), - QuillNativeBridgePlatformFeature.copyHTMLToClipboard => { + QuillNativeBridgePlatformFeature.copyHtmlToClipboard => { TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS, diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart index 848300f45..421e390a5 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart @@ -62,32 +62,32 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { } @override - Future getClipboardHTML() async { + Future getClipboardHtml() async { assert(() { - if (QuillNativeBridgePlatformFeature.getClipboardHTML.isUnsupported) { + if (QuillNativeBridgePlatformFeature.getClipboardHtml.isUnsupported) { throw FlutterError( - 'getClipboardHTML() is currently not supported on $defaultTargetPlatform.', + 'getClipboardHtml() is currently not supported on $defaultTargetPlatform.', ); } return true; }()); final htmlText = - await _methodChannel.invokeMethod('getClipboardHTML'); + await _methodChannel.invokeMethod('getClipboardHtml'); return htmlText; } @override - Future copyHTMLToClipboard(String html) async { + Future copyHtmlToClipboard(String html) async { assert(() { - if (QuillNativeBridgePlatformFeature.copyHTMLToClipboard.isUnsupported) { + if (QuillNativeBridgePlatformFeature.copyHtmlToClipboard.isUnsupported) { throw FlutterError( - 'copyHTMLToClipboard() is currently not supported on $defaultTargetPlatform.', + 'copyHtmlToClipboard() is currently not supported on $defaultTargetPlatform.', ); } return true; }()); await _methodChannel.invokeMethod( - 'copyHTMLToClipboard', + 'copyHtmlToClipboard', html, ); } diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart index 60401edb1..a3c3110d4 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart @@ -11,14 +11,14 @@ class MockQuillNativeBridgePlatform Future isIOSSimulator() async => false; @override - Future getClipboardHTML() async { + Future getClipboardHtml() async { return '
Invalid HTML
'; } String? primaryHTMLClipbaord; @override - Future copyHTMLToClipboard(String html) async { + Future copyHtmlToClipboard(String html) async { primaryHTMLClipbaord = html; } @@ -55,9 +55,9 @@ void main() { expect(await QuillNativeBridgePlatform.instance.isIOSSimulator(), false); }); - test('getClipboardHTML()', () async { + test('getClipboardHtml()', () async { expect( - await QuillNativeBridgePlatform.instance.getClipboardHTML(), + await QuillNativeBridgePlatform.instance.getClipboardHtml(), '
Invalid HTML
', ); }); @@ -75,13 +75,13 @@ void main() { ); }); - test('copyHTMLToClipboard()', () async { + test('copyHtmlToClipboard()', () async { const html = '
HTML
'; expect( fakePlatform.primaryHTMLClipbaord, null, ); - await QuillNativeBridgePlatform.instance.copyHTMLToClipboard(html); + await QuillNativeBridgePlatform.instance.copyHtmlToClipboard(html); expect( fakePlatform.primaryHTMLClipbaord, html, diff --git a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart index ace2df795..4ced585df 100644 --- a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart +++ b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart @@ -31,7 +31,7 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { } @override - Future getClipboardHTML() async { + Future getClipboardHtml() async { if (isClipbaordApiUnsupported) { throw UnsupportedError( 'Could not retrieve HTML from the clipboard.\n' @@ -51,7 +51,7 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { } @override - Future copyHTMLToClipboard(String html) async { + Future copyHtmlToClipboard(String html) async { if (isClipbaordApiUnsupported) { throw UnsupportedError( 'Could not copy HTML to the clipboard.\n' diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index d94e75b89..af90daf9f 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -45,7 +45,7 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { static const _kHtmlFormatName = 'HTML Format'; @override - Future getClipboardHTML() async { + Future getClipboardHtml() async { if (OpenClipboard(NULL) == FALSE) { assert(false, 'Unknown error while opening the clipboard.'); return null; From 7321ef54ec3b59b3c8d6fcd36e3d8b59404d87c2 Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 27 Sep 2024 01:45:11 +0300 Subject: [PATCH 57/90] feat: more advance method to check the platform support check allowing each platform to have it's own check, publish new experimental version of quill_native_version --- .../clipboard/default_clipboard_service.dart | 14 +- pubspec.yaml | 2 +- .../quill_native_bridge/CHANGELOG.md | 4 + .../quill_native_bridge/README.md | 11 +- .../quill_native_bridge/example/lib/main.dart | 36 +++-- .../lib/quill_native_bridge.dart | 22 ++- .../quill_native_bridge/pubspec.yaml | 16 +- .../quill_native_bridge_android/CHANGELOG.md | 4 + .../lib/quill_native_bridge_android.dart | 19 +++ .../quill_native_bridge_android/pubspec.yaml | 4 +- .../quill_native_bridge_ios/CHANGELOG.md | 4 + .../lib/quill_native_bridge_ios.dart | 18 +++ .../quill_native_bridge_ios/pubspec.yaml | 4 +- .../quill_native_bridge_linux/CHANGELOG.md | 3 + .../lib/quill_native_bridge_linux.dart | 20 +++ .../quill_native_bridge_linux/pubspec.yaml | 4 +- .../quill_native_bridge_macos/CHANGELOG.md | 4 + .../lib/quill_native_bridge_macos.dart | 20 +++ .../quill_native_bridge_macos/pubspec.yaml | 4 +- .../CHANGELOG.md | 7 + ...uill_native_bridge_platform_interface.dart | 20 +++ .../lib/src/platform_feature.dart | 151 +++++++++--------- .../quill_native_bridge_method_channel.dart | 59 ++----- .../pubspec.yaml | 2 +- .../test/quill_native_bridge_test.dart | 6 + .../quill_native_bridge_web/CHANGELOG.md | 4 + .../lib/quill_native_bridge_web.dart | 20 +++ .../quill_native_bridge_web/pubspec.yaml | 4 +- .../quill_native_bridge_windows/CHANGELOG.md | 4 + .../lib/quill_native_bridge_windows.dart | 20 +++ .../quill_native_bridge_windows/pubspec.yaml | 4 +- 31 files changed, 344 insertions(+), 170 deletions(-) diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart index 00875a1f9..fe2873371 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart @@ -1,7 +1,7 @@ import 'package:flutter/services.dart' show Uint8List; import 'package:meta/meta.dart' show experimental; import 'package:quill_native_bridge/quill_native_bridge.dart' - show QuillNativeBridge, QuillNativeBridgePlatformFeature; + show QuillNativeBridge, QuillNativeBridgeFeature; import 'clipboard_service.dart'; @@ -11,7 +11,8 @@ import 'clipboard_service.dart'; class DefaultClipboardService extends ClipboardService { @override Future getHtmlText() async { - if (QuillNativeBridgePlatformFeature.getClipboardHtml.isUnsupported) { + if (!(await QuillNativeBridge.isSupported( + QuillNativeBridgeFeature.getClipboardHtml))) { return null; } return await QuillNativeBridge.getClipboardHtml(); @@ -19,7 +20,8 @@ class DefaultClipboardService extends ClipboardService { @override Future getImageFile() async { - if (QuillNativeBridgePlatformFeature.getClipboardImage.isUnsupported) { + if (!(await QuillNativeBridge.isSupported( + QuillNativeBridgeFeature.getClipboardImage))) { return null; } return await QuillNativeBridge.getClipboardImage(); @@ -27,7 +29,8 @@ class DefaultClipboardService extends ClipboardService { @override Future copyImageToClipboard(Uint8List imageBytes) async { - if (QuillNativeBridgePlatformFeature.copyImageToClipboard.isUnsupported) { + if (!(await QuillNativeBridge.isSupported( + QuillNativeBridgeFeature.copyImageToClipboard))) { return; } await QuillNativeBridge.copyImageToClipboard(imageBytes); @@ -35,7 +38,8 @@ class DefaultClipboardService extends ClipboardService { @override Future getGifFile() async { - if (QuillNativeBridgePlatformFeature.getClipboardGif.isUnsupported) { + if (!(await QuillNativeBridge.isSupported( + QuillNativeBridgeFeature.getClipboardGif))) { return null; } return QuillNativeBridge.getClipboardGif(); diff --git a/pubspec.yaml b/pubspec.yaml index 909d48120..76beb79ae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: # Plugins url_launcher: ^6.2.4 flutter_keyboard_visibility: ^6.0.0 - quill_native_bridge: ^10.7.6 + quill_native_bridge: ^10.7.7 dev_dependencies: flutter_lints: ^4.0.0 diff --git a/quill_native_bridge/quill_native_bridge/CHANGELOG.md b/quill_native_bridge/quill_native_bridge/CHANGELOG.md index 28c1456de..448e71846 100644 --- a/quill_native_bridge/quill_native_bridge/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 10.7.7 + +- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. + ## 10.7.6 - Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. diff --git a/quill_native_bridge/quill_native_bridge/README.md b/quill_native_bridge/quill_native_bridge/README.md index 216dfc41e..3886cb94b 100644 --- a/quill_native_bridge/quill_native_bridge/README.md +++ b/quill_native_bridge/quill_native_bridge/README.md @@ -3,4 +3,13 @@ An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) package to access platform-specific APIs. > [!NOTE] -> **Internal Use Only**: Exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file +> **Internal Use Only**: Exclusively for `flutter_quill`. Breaking changes may occur. + +| Feature | iOS | Android | macOS | Windows | Linux | Web | Description | +|------------------------------|------|---------|-------|---------|-------|--------|---------------------------------------------------------------------------------------------------------| +| **isIOSSimulator** | Yes | No | No | No | No | No | Checks if the code is running in an iOS simulator. | +| **getClipboardHtml** | Yes | Yes | Yes | Yes | Yes | Yes | Retrieves HTML content from the system clipboard. | +| **copyHtmlToClipboard** | Yes | Yes | Yes | No | Yes | Yes | Copies HTML content to the system clipboard. | +| **copyImageToClipboard** | Yes | Yes | Yes | No | Yes | Yes | Copies an image to the system clipboard. | +| **getClipboardImage** | Yes | Yes | Yes | No | Yes | Yes | Retrieves an image from the system clipboard. | +| **getClipboardGif** | Yes | Yes | No | No | No | No | Retrieves a GIF from the system clipboard. | diff --git a/quill_native_bridge/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/quill_native_bridge/example/lib/main.dart index 35d2d8585..ebe0ef1a2 100644 --- a/quill_native_bridge/quill_native_bridge/example/lib/main.dart +++ b/quill_native_bridge/quill_native_bridge/example/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb; import 'package:flutter/material.dart'; import 'package:quill_native_bridge/quill_native_bridge.dart' - show QuillNativeBridge, QuillNativeBridgePlatformFeature; + show QuillNativeBridge, QuillNativeBridgeFeature; import 'assets.dart'; @@ -42,7 +42,7 @@ class Buttons extends StatelessWidget { const SizedBox(height: 50), ElevatedButton.icon( onPressed: () => _onButtonClick( - QuillNativeBridgePlatformFeature.isIOSSimulator, + QuillNativeBridgeFeature.isIOSSimulator, context: context, ), label: const Text('Is iOS Simulator'), @@ -50,7 +50,7 @@ class Buttons extends StatelessWidget { ), ElevatedButton.icon( onPressed: () => _onButtonClick( - QuillNativeBridgePlatformFeature.getClipboardHtml, + QuillNativeBridgeFeature.getClipboardHtml, context: context, ), label: const Text('Get HTML from Clipboard'), @@ -58,7 +58,7 @@ class Buttons extends StatelessWidget { ), ElevatedButton.icon( onPressed: () => _onButtonClick( - QuillNativeBridgePlatformFeature.copyHtmlToClipboard, + QuillNativeBridgeFeature.copyHtmlToClipboard, context: context, ), label: const Text('Copy HTML to Clipboard'), @@ -66,7 +66,7 @@ class Buttons extends StatelessWidget { ), ElevatedButton.icon( onPressed: () => _onButtonClick( - QuillNativeBridgePlatformFeature.copyImageToClipboard, + QuillNativeBridgeFeature.copyImageToClipboard, context: context, ), label: const Text('Copy Image to Clipboard'), @@ -74,7 +74,7 @@ class Buttons extends StatelessWidget { ), ElevatedButton.icon( onPressed: () => _onButtonClick( - QuillNativeBridgePlatformFeature.getClipboardImage, + QuillNativeBridgeFeature.getClipboardImage, context: context, ), label: const Text('Retrieve Image from Clipboard'), @@ -82,7 +82,7 @@ class Buttons extends StatelessWidget { ), ElevatedButton.icon( onPressed: () => _onButtonClick( - QuillNativeBridgePlatformFeature.getClipboardGif, + QuillNativeBridgeFeature.getClipboardGif, context: context, ), label: const Text('Retrieve Gif from Clipboard'), @@ -93,14 +93,16 @@ class Buttons extends StatelessWidget { } Future _onButtonClick( - QuillNativeBridgePlatformFeature platformFeature, { + QuillNativeBridgeFeature feature, { required BuildContext context, }) async { - final isFeatureUnsupported = platformFeature.isUnsupported; - final isFeatureWebUnsupported = !platformFeature.hasWebSupport && kIsWeb; final scaffoldMessenger = ScaffoldMessenger.of(context); - switch (platformFeature) { - case QuillNativeBridgePlatformFeature.isIOSSimulator: + + final isFeatureUnsupported = + !(await QuillNativeBridge.isSupported(feature)); + final isFeatureWebUnsupported = isFeatureUnsupported && kIsWeb; + switch (feature) { + case QuillNativeBridgeFeature.isIOSSimulator: if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported @@ -114,7 +116,7 @@ class Buttons extends StatelessWidget { ? "You're running the app on iOS simulator" : "You're running the app on real iOS device."); break; - case QuillNativeBridgePlatformFeature.getClipboardHtml: + case QuillNativeBridgeFeature.getClipboardHtml: if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported @@ -135,7 +137,7 @@ class Buttons extends StatelessWidget { ); debugPrint('HTML from the clipboard: $result'); break; - case QuillNativeBridgePlatformFeature.copyHtmlToClipboard: + case QuillNativeBridgeFeature.copyHtmlToClipboard: if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported @@ -156,7 +158,7 @@ class Buttons extends StatelessWidget { 'HTML copied to the clipboard: $html', ); break; - case QuillNativeBridgePlatformFeature.copyImageToClipboard: + case QuillNativeBridgeFeature.copyImageToClipboard: if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported @@ -185,7 +187,7 @@ class Buttons extends StatelessWidget { 'Image has been copied to the clipboard.', ); break; - case QuillNativeBridgePlatformFeature.getClipboardImage: + case QuillNativeBridgeFeature.getClipboardImage: if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported @@ -211,7 +213,7 @@ class Buttons extends StatelessWidget { ), ); break; - case QuillNativeBridgePlatformFeature.getClipboardGif: + case QuillNativeBridgeFeature.getClipboardGif: if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported diff --git a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart index 4165f179e..5e6dae9e3 100644 --- a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart @@ -5,9 +5,8 @@ import 'package:flutter/foundation.dart' import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; -// TODO: Might move platform feature check outside of quill_native_bridge_platform_interface -// to allow the implementation of QuillNativeBridgePlatform to have a different check. -export 'package:quill_native_bridge_platform_interface/src/platform_feature.dart'; +export 'package:quill_native_bridge_platform_interface/src/platform_feature.dart' + show QuillNativeBridgeFeature; /// An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) /// package to access platform-specific APIs. @@ -25,6 +24,23 @@ class QuillNativeBridge { /// is [TargetPlatform.iOS] and [kIsWeb] is `false`. static Future isIOSSimulator() => _platform.isIOSSimulator(); + /// Checks if the specified [feature] is supported in the current implementation. + /// + /// Will verify if this is supported in the platform itself: + /// + /// - If [feature] is supported on **Android API 21** (as an example) and the + /// current Android API is `19` then will return `false` + /// - If [feature] is supported on the web if Clipboard API (as another example) + /// available in the current browser, and the current browser doesn't support it, + /// will return `false` too. For this specific example, you will need + /// to fallback to **Clipboard events** on **Firefox** or browsers that doesn't + /// support **Clipboard API**. + /// + /// Always check the docs of the method you're calling to see if there + /// are special notes. + static Future isSupported(QuillNativeBridgeFeature feature) => + _platform.isSupported(feature); + /// Return HTML from the Clipboard. /// /// **Important for web**: If [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) diff --git a/quill_native_bridge/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml index ebb673bde..a6e1df4fd 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge description: "An internal plugin for flutter_quill package to access platform-specific APIs" -version: 10.7.6 +version: 10.7.7 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -21,13 +21,13 @@ environment: dependencies: flutter: sdk: flutter - quill_native_bridge_android: ^0.0.1-dev.0 - quill_native_bridge_platform_interface: ^0.0.1-dev.0 - quill_native_bridge_web: ^0.0.1-dev.1 - quill_native_bridge_windows: ^0.0.1-dev.0 - quill_native_bridge_linux: ^0.0.1-dev.0 - quill_native_bridge_ios: ^0.0.1-dev.0 - quill_native_bridge_macos: ^0.0.1-dev.0 + quill_native_bridge_android: ^0.0.1-dev.1 + quill_native_bridge_platform_interface: ^0.0.1-dev.2 + quill_native_bridge_web: ^0.0.1-dev.2 + quill_native_bridge_windows: ^0.0.1-dev.1 + quill_native_bridge_linux: ^0.0.1-dev.1 + quill_native_bridge_ios: ^0.0.1-dev.1 + quill_native_bridge_macos: ^0.0.1-dev.1 dev_dependencies: flutter_test: diff --git a/quill_native_bridge/quill_native_bridge_android/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_android/CHANGELOG.md index f3225b75a..13b5ca517 100644 --- a/quill_native_bridge/quill_native_bridge_android/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge_android/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 0.0.1-dev.1 + +- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. + ## 0.0.1-dev.0 - Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart index c6472c09d..dbf196036 100644 --- a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart +++ b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart @@ -35,6 +35,25 @@ class QuillNativeBridgeAndroid extends QuillNativeBridgePlatform { QuillNativeBridgePlatform.instance = QuillNativeBridgeAndroid._(); } + @override + Future isSupported(QuillNativeBridgeFeature feature) async { + switch (feature) { + case QuillNativeBridgeFeature.isIOSSimulator: + return false; + case QuillNativeBridgeFeature.getClipboardHtml: + case QuillNativeBridgeFeature.copyHtmlToClipboard: + case QuillNativeBridgeFeature.copyImageToClipboard: + case QuillNativeBridgeFeature.getClipboardImage: + case QuillNativeBridgeFeature.getClipboardGif: + return true; + // Without this default check, adding new item to the enum will be a breaking change + default: + throw UnimplementedError( + 'Checking if `${feature.name}` is supported on Android is not covered.', + ); + } + } + @override Future getClipboardHtml() async => _hostApi.getClipboardHtml(); diff --git a/quill_native_bridge/quill_native_bridge_android/pubspec.yaml b/quill_native_bridge/quill_native_bridge_android/pubspec.yaml index f0dfbbc62..d3b568946 100644 --- a/quill_native_bridge/quill_native_bridge_android/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_android/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge_android description: "Android implementation of the quill_native_bridge plugin." -version: 0.0.1-dev.0 +version: 0.0.1-dev.1 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_android repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_android issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -13,7 +13,7 @@ environment: dependencies: flutter: sdk: flutter - quill_native_bridge_platform_interface: ^0.0.1-dev.0 + quill_native_bridge_platform_interface: ^0.0.1-dev.2 dev_dependencies: flutter_test: diff --git a/quill_native_bridge/quill_native_bridge_ios/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_ios/CHANGELOG.md index f3225b75a..13b5ca517 100644 --- a/quill_native_bridge/quill_native_bridge_ios/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge_ios/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 0.0.1-dev.1 + +- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. + ## 0.0.1-dev.0 - Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart b/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart index d6f34c4ce..a5fc7364c 100644 --- a/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart +++ b/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart @@ -34,6 +34,24 @@ class QuillNativeBridgeIos extends QuillNativeBridgePlatform { QuillNativeBridgePlatform.instance = QuillNativeBridgeIos._(); } + @override + Future isSupported(QuillNativeBridgeFeature feature) async { + switch (feature) { + case QuillNativeBridgeFeature.isIOSSimulator: + case QuillNativeBridgeFeature.getClipboardHtml: + case QuillNativeBridgeFeature.copyHtmlToClipboard: + case QuillNativeBridgeFeature.copyImageToClipboard: + case QuillNativeBridgeFeature.getClipboardImage: + case QuillNativeBridgeFeature.getClipboardGif: + return true; + // Without this default check, adding new item to the enum will be a breaking change + default: + throw UnimplementedError( + 'Checking if `${feature.name}` is supported on iOS is not covered.', + ); + } + } + @override Future isIOSSimulator() => _hostApi.isIosSimulator(); diff --git a/quill_native_bridge/quill_native_bridge_ios/pubspec.yaml b/quill_native_bridge/quill_native_bridge_ios/pubspec.yaml index a3b5edd69..3007ad345 100644 --- a/quill_native_bridge/quill_native_bridge_ios/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_ios/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge_ios description: "iOS implementation of the quill_native_bridge plugin." -version: 0.0.1-dev.0 +version: 0.0.1-dev.1 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_ios repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_ios issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -13,7 +13,7 @@ environment: dependencies: flutter: sdk: flutter - quill_native_bridge_platform_interface: ^0.0.1-dev.0 + quill_native_bridge_platform_interface: ^0.0.1-dev.2 dev_dependencies: flutter_test: diff --git a/quill_native_bridge/quill_native_bridge_linux/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_linux/CHANGELOG.md index 167a42b26..13b5ca517 100644 --- a/quill_native_bridge/quill_native_bridge_linux/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge_linux/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## 0.0.1-dev.1 + +- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. ## 0.0.1-dev.0 diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart b/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart index a07d35ca1..ca34ae16f 100644 --- a/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart +++ b/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart @@ -32,6 +32,26 @@ class QuillNativeBridgeLinux extends QuillNativeBridgePlatform { QuillNativeBridgePlatform.instance = QuillNativeBridgeLinux._(); } + @override + Future isSupported(QuillNativeBridgeFeature feature) async { + switch (feature) { + case QuillNativeBridgeFeature.isIOSSimulator: + return false; + case QuillNativeBridgeFeature.getClipboardHtml: + case QuillNativeBridgeFeature.copyHtmlToClipboard: + case QuillNativeBridgeFeature.copyImageToClipboard: + case QuillNativeBridgeFeature.getClipboardImage: + return true; + case QuillNativeBridgeFeature.getClipboardGif: + return false; + // Without this default check, adding new item to the enum will be a breaking change + default: + throw UnimplementedError( + 'Checking if `${feature.name}` is supported on Linux is not covered.', + ); + } + } + // TODO: Improve error handling // TODO: The xclipFile should always be removed in finally block, extractBinaryFromAsset() diff --git a/quill_native_bridge/quill_native_bridge_linux/pubspec.yaml b/quill_native_bridge/quill_native_bridge_linux/pubspec.yaml index c5f1e8ae6..3ec65aa4d 100644 --- a/quill_native_bridge/quill_native_bridge_linux/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_linux/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge_linux description: "Linux implementation of the quill_native_bridge plugin." -version: 0.0.1-dev.0 +version: 0.0.1-dev.1 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_linux repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_linux issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -13,7 +13,7 @@ environment: dependencies: flutter: sdk: flutter - quill_native_bridge_platform_interface: ^0.0.1-dev.0 + quill_native_bridge_platform_interface: ^0.0.1-dev.2 dev_dependencies: flutter_test: diff --git a/quill_native_bridge/quill_native_bridge_macos/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_macos/CHANGELOG.md index f3225b75a..13b5ca517 100644 --- a/quill_native_bridge/quill_native_bridge_macos/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge_macos/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 0.0.1-dev.1 + +- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. + ## 0.0.1-dev.0 - Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart b/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart index 955092ba1..c48d98169 100644 --- a/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart +++ b/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart @@ -34,6 +34,26 @@ class QuillNativeBridgeMacOS extends QuillNativeBridgePlatform { QuillNativeBridgePlatform.instance = QuillNativeBridgeMacOS._(); } + @override + Future isSupported(QuillNativeBridgeFeature feature) async { + switch (feature) { + case QuillNativeBridgeFeature.isIOSSimulator: + return false; + case QuillNativeBridgeFeature.getClipboardHtml: + case QuillNativeBridgeFeature.copyHtmlToClipboard: + case QuillNativeBridgeFeature.copyImageToClipboard: + case QuillNativeBridgeFeature.getClipboardImage: + return true; + case QuillNativeBridgeFeature.getClipboardGif: + return false; + // Without this default check, adding new item to the enum will be a breaking change + default: + throw UnimplementedError( + 'Checking if `${feature.name}` is supported on macOS is not covered.', + ); + } + } + @override Future isIOSSimulator() => throw UnsupportedError( 'isIOSSimulator() is only supported on iOS.', diff --git a/quill_native_bridge/quill_native_bridge_macos/pubspec.yaml b/quill_native_bridge/quill_native_bridge_macos/pubspec.yaml index d1078ca07..00c8a7279 100644 --- a/quill_native_bridge/quill_native_bridge_macos/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_macos/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge_macos description: "macOS implementation of the quill_native_bridge plugin." -version: 0.0.1-dev.0 +version: 0.0.1-dev.1 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_macos repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_macos issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -13,7 +13,7 @@ environment: dependencies: flutter: sdk: flutter - quill_native_bridge_platform_interface: ^0.0.1-dev.0 + quill_native_bridge_platform_interface: ^0.0.1-dev.2 dev_dependencies: flutter_test: diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_platform_interface/CHANGELOG.md index 167a42b26..9ccec2f2d 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge_platform_interface/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## 0.0.1-dev.2 + +- Identical to `0.0.1-dev.1` + +## 0.0.1-dev.1 + +- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. ## 0.0.1-dev.0 diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart index b132d32d1..7316bb618 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart @@ -1,8 +1,11 @@ import 'package:flutter/foundation.dart' show Uint8List; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'src/platform_feature.dart'; import 'src/quill_native_bridge_method_channel.dart'; +export 'src/platform_feature.dart'; + /// **Experimental** as breaking changes can occur. /// /// Platform implementations should extend this class rather than implement it @@ -37,6 +40,23 @@ abstract class QuillNativeBridgePlatform extends PlatformInterface { _instance = instance; } + /// Checks if the specified [feature] is supported in the current implementation. + /// + /// Will verify if this is supported in the platform itself: + /// + /// - If [feature] is supported on **Android API 21** (as an example) and the + /// current Android API is `19` then will return `false` + /// - If [feature] is supported on the web if Clipboard API (as another example) + /// available in the current browser, and the current browser doesn't support it, + /// will return `false` too. For this specific example, you will need + /// to fallback to **Clipboard events** on **Firefox** or browsers that doesn't + /// support **Clipboard API**. + /// + /// Always check the docs of the method you're calling to see if there + /// are special notes. + Future isSupported(QuillNativeBridgeFeature feature) => + throw UnimplementedError('isSupported() has not been implemented.'); + /// Check if the app is running on [iOS Simulator](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device). Future isIOSSimulator() => throw UnimplementedError('isIOSSimulator() has not been implemented.'); diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart index 2395bdec4..120c3636b 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart @@ -1,82 +1,81 @@ -import 'package:flutter/foundation.dart' - show TargetPlatform, defaultTargetPlatform, kIsWeb; +/// The platform features provided by the plugin +enum QuillNativeBridgeFeature { + isIOSSimulator(), + getClipboardHtml(), + copyHtmlToClipboard(), + copyImageToClipboard(), + getClipboardImage(), + getClipboardGif(); -/// The features/methods provided by the plugin -enum QuillNativeBridgePlatformFeature { - isIOSSimulator(hasWebSupport: false), - getClipboardHtml(hasWebSupport: true), - copyHtmlToClipboard(hasWebSupport: true), - copyImageToClipboard(hasWebSupport: true), - getClipboardImage(hasWebSupport: true), - getClipboardGif(hasWebSupport: false); + const QuillNativeBridgeFeature(); - const QuillNativeBridgePlatformFeature({required this.hasWebSupport}); + // TODO: Remove those comments later - /// Verify if this feature is supported on web regardless of the [TargetPlatform]. - /// - /// **Note**: This doesn't check whatever if the web browser support this - /// specific feature. - /// - /// For example the **Clipboard API** might not be supported on **Firefox** - /// but is supported on the web itself in general, the [hasWebSupport] - /// will return `true`. - /// - /// Always check the docs of the method you're calling to see if there - /// are special notes. For this specific example, you will need - /// to fallback to **Clipboard events** on **Firefox** or browsers that doesn't - /// support **Clipboard API**. - final bool hasWebSupport; + // /// Verify if this feature is supported on web regardless of the [TargetPlatform]. + // /// + // /// **Note**: This doesn't check whatever if the web browser support this + // /// specific feature. + // /// + // /// For example the **Clipboard API** might not be supported on **Firefox** + // /// but is supported on the web itself in general, the [hasWebSupport] + // /// will return `true`. + // /// + // /// Always check the docs of the method you're calling to see if there + // /// are special notes. For this specific example, you will need + // /// to fallback to **Clipboard events** on **Firefox** or browsers that doesn't + // /// support **Clipboard API**. + // final bool hasWebSupport; - /// Verify whether a specific feature is supported by the plugin for the [TargetPlatform]. - /// - /// **Note**: This doesn't check if the platform operating system does support - /// this feature. It only check if this feature is supported - /// on a specific platform (e.g. **Android** or **iOS**). - /// - /// If feature A is not supported on **Android API 21** (for example), - /// then the [isSupported] doesn't cover this case. - /// - /// Always check the docs of the method you're calling to see if there - /// are special notes. - bool get isSupported { - if (kIsWeb) { - return hasWebSupport; - } - return switch (this) { - QuillNativeBridgePlatformFeature.isIOSSimulator => - !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS, - QuillNativeBridgePlatformFeature.getClipboardHtml => { - TargetPlatform.android, - TargetPlatform.iOS, - TargetPlatform.macOS, - TargetPlatform.windows, - TargetPlatform.linux, - }.contains(defaultTargetPlatform), - QuillNativeBridgePlatformFeature.copyHtmlToClipboard => { - TargetPlatform.android, - TargetPlatform.iOS, - TargetPlatform.macOS, - TargetPlatform.linux, - }.contains(defaultTargetPlatform), - QuillNativeBridgePlatformFeature.copyImageToClipboard => { - TargetPlatform.android, - TargetPlatform.iOS, - TargetPlatform.macOS, - TargetPlatform.linux, - }.contains(defaultTargetPlatform), - QuillNativeBridgePlatformFeature.getClipboardImage => { - TargetPlatform.android, - TargetPlatform.iOS, - TargetPlatform.macOS, - TargetPlatform.linux, - }.contains(defaultTargetPlatform), - QuillNativeBridgePlatformFeature.getClipboardGif => { - TargetPlatform.android, - TargetPlatform.iOS - }.contains(defaultTargetPlatform), - }; - } + // /// Verify whether a specific feature is supported by the plugin for the [TargetPlatform]. + // /// + // /// **Note**: This doesn't check if the platform operating system does support + // /// this feature. It only check if this feature is supported + // /// on a specific platform (e.g. **Android** or **iOS**). + // /// + // /// If feature A is not supported on **Android API 21** (for example), + // /// then the [isSupported] doesn't cover this case. + // /// + // /// Always check the docs of the method you're calling to see if there + // /// are special notes. + // bool get isSupported { + // if (kIsWeb) { + // return hasWebSupport; + // } + // return switch (this) { + // QuillNativeBridgeFeature.isIOSSimulator => + // !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS, + // QuillNativeBridgeFeature.getClipboardHtml => { + // TargetPlatform.android, + // TargetPlatform.iOS, + // TargetPlatform.macOS, + // TargetPlatform.windows, + // TargetPlatform.linux, + // }.contains(defaultTargetPlatform), + // QuillNativeBridgeFeature.copyHtmlToClipboard => { + // TargetPlatform.android, + // TargetPlatform.iOS, + // TargetPlatform.macOS, + // TargetPlatform.linux, + // }.contains(defaultTargetPlatform), + // QuillNativeBridgeFeature.copyImageToClipboard => { + // TargetPlatform.android, + // TargetPlatform.iOS, + // TargetPlatform.macOS, + // TargetPlatform.linux, + // }.contains(defaultTargetPlatform), + // QuillNativeBridgeFeature.getClipboardImage => { + // TargetPlatform.android, + // TargetPlatform.iOS, + // TargetPlatform.macOS, + // TargetPlatform.linux, + // }.contains(defaultTargetPlatform), + // QuillNativeBridgeFeature.getClipboardGif => { + // TargetPlatform.android, + // TargetPlatform.iOS + // }.contains(defaultTargetPlatform), + // }; + // } - /// Negation of [isSupported] - bool get isUnsupported => !isSupported; + // /// Negation of [isSupported] + // bool get isUnsupported => !isSupported; } diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart index 421e390a5..b1fc96ade 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart @@ -6,11 +6,9 @@ import 'package:flutter/services.dart' show MethodChannel; import '../quill_native_bridge_platform_interface.dart'; import 'platform_feature.dart'; -// TODO: This was only for iOS, Android, and macOS, now it's no longer needed -// for Android, will be no longer used for iOS and macOS either soon. - -// TODO: Platform-specific check like if this is supported should be removed from -// here as discussed in https://github.com/singerdmx/flutter-quill/pull/2230 +// TODO: This class is no longer used for implementations that use method channel. +// Instead each platform (e.g. Android) have their own implementation which might +// or might not use method channel, might remove this class completely const _methodChannel = MethodChannel('quill_native_bridge'); @@ -38,10 +36,19 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { return _methodChannel; } + @override + Future isSupported(QuillNativeBridgeFeature feature) async { + final isSupported = await _methodChannel.invokeMethod( + 'isSupported', + feature.name, + ); + return isSupported ?? false; + } + @override Future isIOSSimulator() async { assert(() { - if (QuillNativeBridgePlatformFeature.isIOSSimulator.isUnsupported) { + if (defaultTargetPlatform != TargetPlatform.iOS || kIsWeb) { throw FlutterError( 'isIOSSimulator() method should be called only on iOS.', ); @@ -63,14 +70,6 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { @override Future getClipboardHtml() async { - assert(() { - if (QuillNativeBridgePlatformFeature.getClipboardHtml.isUnsupported) { - throw FlutterError( - 'getClipboardHtml() is currently not supported on $defaultTargetPlatform.', - ); - } - return true; - }()); final htmlText = await _methodChannel.invokeMethod('getClipboardHtml'); return htmlText; @@ -78,14 +77,6 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { @override Future copyHtmlToClipboard(String html) async { - assert(() { - if (QuillNativeBridgePlatformFeature.copyHtmlToClipboard.isUnsupported) { - throw FlutterError( - 'copyHtmlToClipboard() is currently not supported on $defaultTargetPlatform.', - ); - } - return true; - }()); await _methodChannel.invokeMethod( 'copyHtmlToClipboard', html, @@ -94,14 +85,6 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { @override Future copyImageToClipboard(Uint8List imageBytes) async { - assert(() { - if (QuillNativeBridgePlatformFeature.copyImageToClipboard.isUnsupported) { - throw FlutterError( - 'copyImageToClipboard() is currently not supported on $defaultTargetPlatform.', - ); - } - return true; - }()); await _methodChannel.invokeMethod( 'copyImageToClipboard', imageBytes, @@ -112,14 +95,6 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { @override Future getClipboardImage() async { - assert(() { - if (QuillNativeBridgePlatformFeature.getClipboardImage.isUnsupported) { - throw FlutterError( - 'getClipboardImage() is currently not supported on $defaultTargetPlatform.', - ); - } - return true; - }()); final imageBytes = await _methodChannel.invokeMethod( 'getClipboardImage', ); @@ -128,14 +103,6 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { @override Future getClipboardGif() async { - assert(() { - if (QuillNativeBridgePlatformFeature.getClipboardGif.isUnsupported) { - throw FlutterError( - 'getClipboardGif() is currently not supported on $defaultTargetPlatform.', - ); - } - return true; - }()); final gifBytes = await _methodChannel.invokeMethod( 'getClipboardGif', ); diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/pubspec.yaml b/quill_native_bridge/quill_native_bridge_platform_interface/pubspec.yaml index 34ccd1e71..d271109cb 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge_platform_interface description: "A common platform interface for the quill_native_bridge plugin." -version: 0.0.1-dev.0 +version: 0.0.1-dev.2 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_platform_interface repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_platform_interface issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart index a3c3110d4..2a19639b5 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart @@ -2,11 +2,17 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; +import 'package:quill_native_bridge_platform_interface/src/platform_feature.dart'; import 'package:quill_native_bridge_platform_interface/src/quill_native_bridge_method_channel.dart'; class MockQuillNativeBridgePlatform with MockPlatformInterfaceMixin implements QuillNativeBridgePlatform { + @override + Future isSupported(QuillNativeBridgeFeature feature) async { + return false; + } + @override Future isIOSSimulator() async => false; diff --git a/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md index 58e751774..600d2ca05 100644 --- a/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 0.0.1-dev.2 + +- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. + ## 0.0.1-dev.1 - Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. diff --git a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart index 4ced585df..6d8349400 100644 --- a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart +++ b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart @@ -30,6 +30,26 @@ class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { QuillNativeBridgePlatform.instance = QuillNativeBridgeWeb._(); } + @override + Future isSupported(QuillNativeBridgeFeature feature) async { + switch (feature) { + case QuillNativeBridgeFeature.isIOSSimulator: + return false; + case QuillNativeBridgeFeature.getClipboardHtml: + case QuillNativeBridgeFeature.copyHtmlToClipboard: + case QuillNativeBridgeFeature.copyImageToClipboard: + case QuillNativeBridgeFeature.getClipboardImage: + return isClipboardApiSupported; + case QuillNativeBridgeFeature.getClipboardGif: + return false; + // Without this default check, adding new item to the enum will be a breaking change + default: + throw UnimplementedError( + 'Checking if `${feature.name}` is supported on the web is not covered.', + ); + } + } + @override Future getClipboardHtml() async { if (isClipbaordApiUnsupported) { diff --git a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml index fe2bbe6c1..c745040d3 100644 --- a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge_web description: "Web platform implementation of quill_native_bridge" -version: 0.0.1-dev.1 +version: 0.0.1-dev.2 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -16,7 +16,7 @@ dependencies: flutter_web_plugins: sdk: flutter web: ^1.0.0 - quill_native_bridge_platform_interface: ^0.0.1-dev.0 + quill_native_bridge_platform_interface: ^0.0.1-dev.2 dev_dependencies: flutter_test: diff --git a/quill_native_bridge/quill_native_bridge_windows/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_windows/CHANGELOG.md index 167a42b26..eb3c50b72 100644 --- a/quill_native_bridge/quill_native_bridge_windows/CHANGELOG.md +++ b/quill_native_bridge/quill_native_bridge_windows/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. +## 0.0.1-dev.1 + +- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. + ## 0.0.1-dev.0 - Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index af90daf9f..b795dee4b 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -44,6 +44,26 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { /// From [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format). static const _kHtmlFormatName = 'HTML Format'; + @override + Future isSupported(QuillNativeBridgeFeature feature) async { + switch (feature) { + case QuillNativeBridgeFeature.isIOSSimulator: + return false; + case QuillNativeBridgeFeature.getClipboardHtml: + return true; + case QuillNativeBridgeFeature.copyHtmlToClipboard: + case QuillNativeBridgeFeature.copyImageToClipboard: + case QuillNativeBridgeFeature.getClipboardImage: + case QuillNativeBridgeFeature.getClipboardGif: + return false; + // Without this default check, adding new item to the enum will be a breaking change + default: + throw UnimplementedError( + 'Checking if `${feature.name}` is supported on Windows is not covered.', + ); + } + } + @override Future getClipboardHtml() async { if (OpenClipboard(NULL) == FALSE) { diff --git a/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml b/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml index 73c06dd42..b77e84b83 100644 --- a/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_native_bridge_windows description: "Windows implementation of the quill_native_bridge plugin." -version: 0.0.1-dev.0 +version: 0.0.1-dev.1 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_windows repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_windows issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -13,7 +13,7 @@ environment: dependencies: flutter: sdk: flutter - quill_native_bridge_platform_interface: ^0.0.1-dev.0 + quill_native_bridge_platform_interface: ^0.0.1-dev.2 win32: ^5.5.4 ffi: ^2.1.3 From 707d37066e10fe92a7514d5b9929054caa660637 Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 27 Sep 2024 01:48:31 +0300 Subject: [PATCH 58/90] chore(analysis): remove unused imports in quill_native_bridge and quill_native_bridge_platform_interface --- .../lib/src/quill_native_bridge_method_channel.dart | 1 - .../test/quill_native_bridge_test.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart index b1fc96ade..73b116b2a 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart @@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart' show MethodChannel; import '../quill_native_bridge_platform_interface.dart'; -import 'platform_feature.dart'; // TODO: This class is no longer used for implementations that use method channel. // Instead each platform (e.g. Android) have their own implementation which might diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart index 2a19639b5..ea4318246 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart @@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; -import 'package:quill_native_bridge_platform_interface/src/platform_feature.dart'; import 'package:quill_native_bridge_platform_interface/src/quill_native_bridge_method_channel.dart'; class MockQuillNativeBridgePlatform From fd508283e3918b4c8368ebd41d8d3328f7ffae9a Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 27 Sep 2024 01:51:54 +0300 Subject: [PATCH 59/90] fix(ci): temporarily pub get quill_native_bridge packages in main workflow --- .github/workflows/main.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6642e4372..823d31eaf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,9 +36,33 @@ jobs: - name: đŸ“Ļ Install flutter_quill_test dependencies run: flutter pub get -C flutter_quill_test + # TODO: Remove quill_native_bridge packages from here once moved to it's own repo + # https://github.com/singerdmx/flutter-quill/pull/2230 + - name: đŸ“Ļ Install quill_native_bridge dependencies run: flutter pub get -C quill_native_bridge/quill_native_bridge + - name: đŸ“Ļ Install quill_native_bridge_platform_interface dependencies + run: flutter pub get -C quill_native_bridge/quill_native_bridge_platform_interface + + - name: đŸ“Ļ Install quill_native_bridge_android dependencies + run: flutter pub get -C quill_native_bridge/quill_native_bridge_android + + - name: đŸ“Ļ Install quill_native_bridge_ios dependencies + run: flutter pub get -C quill_native_bridge/quill_native_bridge_ios + + - name: đŸ“Ļ Install quill_native_bridge_macos dependencies + run: flutter pub get -C quill_native_bridge/quill_native_bridge_macos + + - name: đŸ“Ļ Install quill_native_bridge_windows dependencies + run: flutter pub get -C quill_native_bridge/quill_native_bridge_windows + + - name: đŸ“Ļ Install quill_native_bridge_web dependencies + run: flutter pub get -C quill_native_bridge/quill_native_bridge_web + + - name: đŸ“Ļ Install quill_native_bridge_linux dependencies + run: flutter pub get -C quill_native_bridge/quill_native_bridge_linux + - name: 🔍 Run Flutter analysis run: flutter analyze From 693928926312307d01313881649ee0e63dd9c80e Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 27 Sep 2024 02:01:27 +0300 Subject: [PATCH 60/90] chore: format dart pigeon generated files as a workaround to CI failure --- .../lib/src/messages.g.dart | 37 ++++++++++------ .../lib/src/messages.g.dart | 43 ++++++++++++------- .../lib/src/messages.g.dart | 37 ++++++++++------ 3 files changed, 76 insertions(+), 41 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart index ee81bc1ca..e6c0d69c6 100644 --- a/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart +++ b/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart @@ -15,7 +15,6 @@ PlatformException _createConnectionError(String channelName) { ); } - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); } @@ -24,9 +23,11 @@ class QuillNativeBridgeApi { /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - QuillNativeBridgeApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + QuillNativeBridgeApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -34,8 +35,10 @@ class QuillNativeBridgeApi { final String pigeonVar_messageChannelSuffix; Future getClipboardHtml() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -56,8 +59,10 @@ class QuillNativeBridgeApi { } Future copyHtmlToClipboard(String html) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -78,8 +83,10 @@ class QuillNativeBridgeApi { } Future getClipboardImage() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -100,8 +107,10 @@ class QuillNativeBridgeApi { } Future copyImageToClipboard(Uint8List imageBytes) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -122,8 +131,10 @@ class QuillNativeBridgeApi { } Future getClipboardGif() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, diff --git a/quill_native_bridge/quill_native_bridge_ios/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_ios/lib/src/messages.g.dart index b3c027981..e12dd5f17 100644 --- a/quill_native_bridge/quill_native_bridge_ios/lib/src/messages.g.dart +++ b/quill_native_bridge/quill_native_bridge_ios/lib/src/messages.g.dart @@ -15,7 +15,6 @@ PlatformException _createConnectionError(String channelName) { ); } - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); } @@ -24,9 +23,11 @@ class QuillNativeBridgeApi { /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - QuillNativeBridgeApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + QuillNativeBridgeApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -34,8 +35,10 @@ class QuillNativeBridgeApi { final String pigeonVar_messageChannelSuffix; Future isIosSimulator() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.isIosSimulator$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.isIosSimulator$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -61,8 +64,10 @@ class QuillNativeBridgeApi { } Future getClipboardHtml() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -83,8 +88,10 @@ class QuillNativeBridgeApi { } Future copyHtmlToClipboard(String html) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -105,8 +112,10 @@ class QuillNativeBridgeApi { } Future getClipboardImage() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -127,8 +136,10 @@ class QuillNativeBridgeApi { } Future copyImageToClipboard(Uint8List imageBytes) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -149,8 +160,10 @@ class QuillNativeBridgeApi { } Future getClipboardGif() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, diff --git a/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart index a2ff9d1e2..7cb36e3bd 100644 --- a/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart +++ b/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart @@ -15,7 +15,6 @@ PlatformException _createConnectionError(String channelName) { ); } - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); } @@ -24,9 +23,11 @@ class QuillNativeBridgeApi { /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - QuillNativeBridgeApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + QuillNativeBridgeApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -34,8 +35,10 @@ class QuillNativeBridgeApi { final String pigeonVar_messageChannelSuffix; Future getClipboardHtml() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -56,8 +59,10 @@ class QuillNativeBridgeApi { } Future copyHtmlToClipboard(String html) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -78,8 +83,10 @@ class QuillNativeBridgeApi { } Future getClipboardImage() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -100,8 +107,10 @@ class QuillNativeBridgeApi { } Future copyImageToClipboard(Uint8List imageBytes) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -122,8 +131,10 @@ class QuillNativeBridgeApi { } Future getClipboardGif() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, From 7847418c8062f5ce8dad2244aa3ed6f736a8797f Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 27 Sep 2024 23:06:27 +0300 Subject: [PATCH 61/90] chore(deps): update flutter_lints to 5.0.0 in quill_native_bridge --- quill_native_bridge/quill_native_bridge/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quill_native_bridge/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml index a6e1df4fd..d6c034697 100644 --- a/quill_native_bridge/quill_native_bridge/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge/pubspec.yaml @@ -32,7 +32,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^4.0.0 + flutter_lints: ^5.0.0 flutter: plugin: From f489acff809f3e4f0808893c0f3d833e186a20db Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 27 Sep 2024 23:35:32 +0300 Subject: [PATCH 62/90] refactor(windows): move CloseClipboard() to finally block in getClipboardHtml() --- .../lib/quill_native_bridge_windows.dart | 105 +++++++++--------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index b795dee4b..1f46dab8e 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -29,21 +29,6 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { QuillNativeBridgePlatform.instance = QuillNativeBridgeWindows._(); } - // TODO: Cleanup this code here - - // TODO: Improve error handling by throwing exception - // instead of using assert, should have a proper way of handling - // errors regardless of this implementation. - - // TODO: Throw exception and always close the clipboard at once - // regardless of the result - - // TODO: Test Clipboard operations with other windows apps and - // see if this implementation causing issues - - /// From [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format). - static const _kHtmlFormatName = 'HTML Format'; - @override Future isSupported(QuillNativeBridgeFeature feature) async { switch (feature) { @@ -64,6 +49,21 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { } } + // TODO: Cleanup this code here + + // TODO: Improve error handling by throwing exception + // instead of using assert, should have a proper way of handling + // errors regardless of this implementation. + + // TODO: Throw exception and always close the clipboard at once + // regardless of the result + + // TODO: Test Clipboard operations with other windows apps and + // see if this implementation causing issues + + /// From [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format). + static const _kHtmlFormatName = 'HTML Format'; + @override Future getClipboardHtml() async { if (OpenClipboard(NULL) == FALSE) { @@ -71,49 +71,48 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { return null; } - final htmlFormatPointer = _kHtmlFormatName.toNativeUtf16(); - final htmlFormatId = RegisterClipboardFormat(htmlFormatPointer); - calloc.free(htmlFormatPointer); + try { + final htmlFormatPointer = _kHtmlFormatName.toNativeUtf16(); + final htmlFormatId = RegisterClipboardFormat(htmlFormatPointer); + calloc.free(htmlFormatPointer); + + if (htmlFormatId == 0) { + assert(false, 'Failed to register clipboard HTML format.'); + return null; + } + + if (IsClipboardFormatAvailable(htmlFormatId) == FALSE) { + return null; + } + + final clipboardDataHandle = GetClipboardData(htmlFormatId); + if (clipboardDataHandle == NULL) { + assert(false, 'Failed to get clipboard data.'); + return null; + } + + final clipboardDataPointer = Pointer.fromAddress(clipboardDataHandle); + final lockedMemoryPointer = GlobalLock(clipboardDataPointer); + if (lockedMemoryPointer == nullptr) { + assert( + false, + 'Failed to lock global memory. Error code: ${GetLastError()}', + ); + return null; + } - if (htmlFormatId == 0) { - CloseClipboard(); - assert(false, 'Failed to register clipboard HTML format.'); - return null; - } + final windowsHtmlWithMetadata = + lockedMemoryPointer.cast().toDartString(); + GlobalUnlock(clipboardDataPointer); - if (IsClipboardFormatAvailable(htmlFormatId) == FALSE) { - CloseClipboard(); - return null; - } + // Strip comments at the start of the HTML as they can cause + // issues while parsing the HTML - final clipboardDataHandle = GetClipboardData(htmlFormatId); - if (clipboardDataHandle == NULL) { - CloseClipboard(); - assert(false, 'Failed to get clipboard data.'); - return null; - } + final cleanedHtml = stripWin32HtmlDescription(windowsHtmlWithMetadata); - final clipboardDataPointer = Pointer.fromAddress(clipboardDataHandle); - final lockedMemoryPointer = GlobalLock(clipboardDataPointer); - if (lockedMemoryPointer == nullptr) { + return cleanedHtml; + } finally { CloseClipboard(); - assert( - false, - 'Failed to lock global memory. Error code: ${GetLastError()}', - ); - return null; } - - final windowsHtmlWithMetadata = - lockedMemoryPointer.cast().toDartString(); - GlobalUnlock(clipboardDataPointer); - CloseClipboard(); - - // Strip comments at the start of the HTML as they can cause - // issues while parsing the HTML - - final cleanedHtml = stripWin32HtmlDescription(windowsHtmlWithMetadata); - - return cleanedHtml; } } From 6257e70d436cc3ebcaf17ebfb0314e4db9b5b428 Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 27 Sep 2024 23:36:34 +0300 Subject: [PATCH 63/90] chore(windows): clear todo related to previous commit --- .../lib/quill_native_bridge_windows.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index 1f46dab8e..6d95a7206 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -55,9 +55,6 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { // instead of using assert, should have a proper way of handling // errors regardless of this implementation. - // TODO: Throw exception and always close the clipboard at once - // regardless of the result - // TODO: Test Clipboard operations with other windows apps and // see if this implementation causing issues From a54ea775192e8bbe8b3b40763d1081df73398970 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 28 Sep 2024 11:36:24 +0300 Subject: [PATCH 64/90] chore(readme): remove description from the platform support table, use emojis instead of text for readability --- .../quill_native_bridge/README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge/README.md b/quill_native_bridge/quill_native_bridge/README.md index 3886cb94b..deb48bca4 100644 --- a/quill_native_bridge/quill_native_bridge/README.md +++ b/quill_native_bridge/quill_native_bridge/README.md @@ -5,11 +5,12 @@ An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) > [!NOTE] > **Internal Use Only**: Exclusively for `flutter_quill`. Breaking changes may occur. -| Feature | iOS | Android | macOS | Windows | Linux | Web | Description | -|------------------------------|------|---------|-------|---------|-------|--------|---------------------------------------------------------------------------------------------------------| -| **isIOSSimulator** | Yes | No | No | No | No | No | Checks if the code is running in an iOS simulator. | -| **getClipboardHtml** | Yes | Yes | Yes | Yes | Yes | Yes | Retrieves HTML content from the system clipboard. | -| **copyHtmlToClipboard** | Yes | Yes | Yes | No | Yes | Yes | Copies HTML content to the system clipboard. | -| **copyImageToClipboard** | Yes | Yes | Yes | No | Yes | Yes | Copies an image to the system clipboard. | -| **getClipboardImage** | Yes | Yes | Yes | No | Yes | Yes | Retrieves an image from the system clipboard. | -| **getClipboardGif** | Yes | Yes | No | No | No | No | Retrieves a GIF from the system clipboard. | +| Feature | iOS | Android | macOS | Windows | Linux | Web | +|--------------------------|------|---------|-------|---------|-------|-------| +| **isIOSSimulator** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **getClipboardHtml** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **copyHtmlToClipboard** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | +| **copyImageToClipboard** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | +| **getClipboardImage** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | +| **getClipboardGif** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | + From 88d6675d1ceba6a89361c2b43f4053c77fc55407 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 28 Sep 2024 11:39:25 +0300 Subject: [PATCH 65/90] chore(readme): use different emoji for platforms that doesn't support a feature or unapplicable in platform support table --- quill_native_bridge/quill_native_bridge/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge/README.md b/quill_native_bridge/quill_native_bridge/README.md index deb48bca4..8096379ae 100644 --- a/quill_native_bridge/quill_native_bridge/README.md +++ b/quill_native_bridge/quill_native_bridge/README.md @@ -7,10 +7,10 @@ An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) | Feature | iOS | Android | macOS | Windows | Linux | Web | |--------------------------|------|---------|-------|---------|-------|-------| -| **isIOSSimulator** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **isIOSSimulator** | ✅ | âšĒ | âšĒ | âšĒ | âšĒ | âšĒ | | **getClipboardHtml** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **copyHtmlToClipboard** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | **copyImageToClipboard** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | **getClipboardImage** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | -| **getClipboardGif** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| **getClipboardGif** | ✅ | ✅ | âšĒ | âšĒ | âšĒ | âšĒ | From 2c2b2839f3e83f68364163d4d86f23c67a9fa6e0 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 28 Sep 2024 12:18:46 +0300 Subject: [PATCH 66/90] style(windows): use TEXT and free from win32 instead of toNativeUtf16() and calloc.free(pointer) from ffi --- .../lib/quill_native_bridge_windows.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index 6d95a7206..3a938fe83 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -69,9 +69,9 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { } try { - final htmlFormatPointer = _kHtmlFormatName.toNativeUtf16(); + final htmlFormatPointer = TEXT(_kHtmlFormatName); final htmlFormatId = RegisterClipboardFormat(htmlFormatPointer); - calloc.free(htmlFormatPointer); + free(htmlFormatPointer); if (htmlFormatId == 0) { assert(false, 'Failed to register clipboard HTML format.'); From 0d572c2136f3c6fa6b01e5132d4ab7bc6707a1cd Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 28 Sep 2024 12:47:39 +0300 Subject: [PATCH 67/90] refactor(windows): only register HTML format id once --- .../lib/quill_native_bridge_windows.dart | 10 ++---- .../lib/src/clipboard_html_format.dart | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 quill_native_bridge/quill_native_bridge_windows/lib/src/clipboard_html_format.dart diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index 3a938fe83..cd5ca5849 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -7,6 +7,7 @@ import 'package:ffi/ffi.dart'; import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; import 'package:win32/win32.dart'; +import 'src/clipboard_html_format.dart'; import 'src/html_cleaner.dart'; /// A Windows implementation of the [QuillNativeBridgePlatform]. @@ -58,9 +59,6 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { // TODO: Test Clipboard operations with other windows apps and // see if this implementation causing issues - /// From [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format). - static const _kHtmlFormatName = 'HTML Format'; - @override Future getClipboardHtml() async { if (OpenClipboard(NULL) == FALSE) { @@ -69,11 +67,9 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { } try { - final htmlFormatPointer = TEXT(_kHtmlFormatName); - final htmlFormatId = RegisterClipboardFormat(htmlFormatPointer); - free(htmlFormatPointer); + final htmlFormatId = cfHtml; - if (htmlFormatId == 0) { + if (htmlFormatId == null) { assert(false, 'Failed to register clipboard HTML format.'); return null; } diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/src/clipboard_html_format.dart b/quill_native_bridge/quill_native_bridge_windows/lib/src/clipboard_html_format.dart new file mode 100644 index 000000000..bb218ec34 --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_windows/lib/src/clipboard_html_format.dart @@ -0,0 +1,31 @@ +import 'package:win32/win32.dart'; + +import '../quill_native_bridge_windows.dart'; + +/// From [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format). +const _kHtmlFormatName = 'HTML Format'; + +int? _cfHtml; + +extension ClipboardHtmlFormatExt on QuillNativeBridgeWindows { + /// The id of [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format). + /// + /// Registers the format if it doesn't exist; returns the existing format. + /// Returns `null` if an error occurs. + int? get cfHtml { + _cfHtml ??= _registerHtmlFormat(); + return _cfHtml; + } + + int? _registerHtmlFormat() { + final htmlFormatPointer = TEXT(_kHtmlFormatName); + final htmlFormatId = RegisterClipboardFormat(htmlFormatPointer); + free(htmlFormatPointer); + + if (htmlFormatId == NULL) { + // When error occurs + return null; + } + return htmlFormatId; + } +} From af80f163d9714602a08fb6c71cbefbe2b9821df0 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 29 Sep 2024 00:42:15 +0300 Subject: [PATCH 68/90] feat(windows): (WIP) highly experimental support for copyHtmlToClipboard(), minor cleanup (need more) --- .../quill_native_bridge/README.md | 2 +- .../lib/quill_native_bridge.dart | 2 +- .../lib/quill_native_bridge_windows.dart | 85 ++++++++++++- .../lib/src/html_cleaner.dart | 19 ++- .../lib/src/html_formatter.dart | 116 ++++++++++++++++++ .../test/html_cleaner_test.dart | 4 +- .../test/html_formatter_test.dart | 23 ++++ 7 files changed, 238 insertions(+), 13 deletions(-) create mode 100644 quill_native_bridge/quill_native_bridge_windows/lib/src/html_formatter.dart create mode 100644 quill_native_bridge/quill_native_bridge_windows/test/html_formatter_test.dart diff --git a/quill_native_bridge/quill_native_bridge/README.md b/quill_native_bridge/quill_native_bridge/README.md index 8096379ae..231e3b995 100644 --- a/quill_native_bridge/quill_native_bridge/README.md +++ b/quill_native_bridge/quill_native_bridge/README.md @@ -9,7 +9,7 @@ An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) |--------------------------|------|---------|-------|---------|-------|-------| | **isIOSSimulator** | ✅ | âšĒ | âšĒ | âšĒ | âšĒ | âšĒ | | **getClipboardHtml** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| **copyHtmlToClipboard** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | +| **copyHtmlToClipboard** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **copyImageToClipboard** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | **getClipboardImage** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | **getClipboardGif** | ✅ | ✅ | âšĒ | âšĒ | âšĒ | âšĒ | diff --git a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart index 5e6dae9e3..0c6ed0d49 100644 --- a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart @@ -61,7 +61,7 @@ class QuillNativeBridge { /// is not supported on the web browser, should fallback to [Clipboard Events](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) /// such as the [copy_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/copy_event). /// - /// Currently only supports **Android**, **iOS**, **macOS**, **Linux**, and the **Web**. + /// Currently only supports **Android**, **iOS**, **macOS**, **Linux**, **Windows** and the **Web**. static Future copyHtmlToClipboard(String html) => _platform.copyHtmlToClipboard(html); diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index cd5ca5849..00b82be77 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -9,6 +9,7 @@ import 'package:win32/win32.dart'; import 'src/clipboard_html_format.dart'; import 'src/html_cleaner.dart'; +import 'src/html_formatter.dart'; /// A Windows implementation of the [QuillNativeBridgePlatform]. /// @@ -32,12 +33,13 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { @override Future isSupported(QuillNativeBridgeFeature feature) async { + // TODO: Extract supported features in Set (do the same for other packages) switch (feature) { case QuillNativeBridgeFeature.isIOSSimulator: return false; case QuillNativeBridgeFeature.getClipboardHtml: - return true; case QuillNativeBridgeFeature.copyHtmlToClipboard: + return true; case QuillNativeBridgeFeature.copyImageToClipboard: case QuillNativeBridgeFeature.getClipboardImage: case QuillNativeBridgeFeature.getClipboardGif: @@ -59,6 +61,9 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { // TODO: Test Clipboard operations with other windows apps and // see if this implementation causing issues + // TODO: Might extract low-level implementation of the clipboard outside of this class + + /// Refer to [Windows GetClipboardData() docs](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclipboarddata) @override Future getClipboardHtml() async { if (OpenClipboard(NULL) == FALSE) { @@ -98,14 +103,88 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { lockedMemoryPointer.cast().toDartString(); GlobalUnlock(clipboardDataPointer); - // Strip comments at the start of the HTML as they can cause + // Strip comments/headers at the start of the HTML as they can cause // issues while parsing the HTML - final cleanedHtml = stripWin32HtmlDescription(windowsHtmlWithMetadata); + final cleanedHtml = + stripWindowsHtmlDescriptionHeaders(windowsHtmlWithMetadata); return cleanedHtml; } finally { CloseClipboard(); } } + + /// Refer to [Windows SetClipboardData() docs](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclipboarddata) + @override + Future copyHtmlToClipboard(String html) async { + if (OpenClipboard(NULL) == FALSE) { + assert(false, 'Unknown error while opening the clipboard.'); + return; + } + + final windowsClipboardHtml = constructWindowsHtmlDescriptionHeaders(html); + final htmlPointer = windowsClipboardHtml.toNativeUtf8(); + + try { + if (EmptyClipboard() == FALSE) { + assert(false, 'Failed to empty the clipboard.'); + return; + } + + final htmlFormatId = cfHtml; + + if (htmlFormatId == null) { + assert(false, 'Failed to register clipboard HTML format.'); + return; + } + + final unitSize = sizeOf(); + final htmlSize = (htmlPointer.length + 1) * unitSize; + + final globalMemoryHandle = + GlobalAlloc(GLOBAL_ALLOC_FLAGS.GMEM_MOVEABLE, htmlSize); + if (globalMemoryHandle == nullptr) { + assert(false, 'Failed to allocate memory for the clipboard content.'); + return; + } + + final lockedMemoryPointer = GlobalLock(globalMemoryHandle); + if (lockedMemoryPointer == nullptr) { + GlobalFree(globalMemoryHandle); + assert( + false, + 'Failed to lock global memory. Error code: ${GetLastError()}', + ); + return; + } + + final targetMemoryPointer = lockedMemoryPointer.cast(); + + final sourcePointer = htmlPointer.cast(); + + // Copy HTML data byte by byte + for (var i = 0; i < htmlPointer.length; i++) { + targetMemoryPointer[i] = (sourcePointer + i).value; + } + (targetMemoryPointer + htmlPointer.length).value = NULL; + + // TODO: Handle errors here, check Win32 docs regarding GlobalUnlock() and SetClipboardData() + // to see any potential issues + + // According to Windows docs in https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclipboarddata#parameters + // Should not unlock when SetClipboardData() success + + if (SetClipboardData(htmlFormatId, lockedMemoryPointer.address) == NULL) { + GlobalUnlock(lockedMemoryPointer); + assert( + false, + 'Failed to set the clipboard data: ${GetLastError()}', + ); + } + } finally { + CloseClipboard(); + calloc.free(htmlPointer); + } + } } diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart b/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart index 088751060..edd8a554f 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart @@ -1,5 +1,10 @@ -/// [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format) -const _kWindowsMetadataHtmlKeys = { +// Used to clean and remove HTML description headers +// when retrieving HTML from the windows clipboard which are +// usually not needed and can cause issues when parsing the HTML. + +/// See [HTML Clipboard Description Headers](https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format#description-headers-and-offsets) +/// for more details. +const _kWindowsDescriptionHeaders = { 'Version', 'StartHTML', 'EndHTML', @@ -9,7 +14,7 @@ const _kWindowsMetadataHtmlKeys = { 'EndSelection' }; -/// Remove the leading description from Windows clipboard HTML. +/// Remove the leading description headers from Windows clipboard HTML. /// /// This function targets specific metadata keys that precede the actual HTML content: /// - `Version` @@ -45,7 +50,9 @@ const _kWindowsMetadataHtmlKeys = { /// /// Refer to [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format) /// for details. -String stripWin32HtmlDescription(String html) { +String stripWindowsHtmlDescriptionHeaders(String html) { + // TODO: Using string indices can be more efficient and more minimal + // to implement using index indexOf() and substring() // Can contains dirty lines final lines = html.split('\n'); @@ -57,9 +64,9 @@ String stripWin32HtmlDescription(String html) { break; } - final isWindowsHtmlMetadata = _kWindowsMetadataHtmlKeys + final isWindowsHtmlDescriptionHeader = _kWindowsDescriptionHeaders .any((metadataKey) => line.startsWith('$metadataKey:')); - if (isWindowsHtmlMetadata) { + if (isWindowsHtmlDescriptionHeader) { cleanedLines.remove(line); continue; } diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/src/html_formatter.dart b/quill_native_bridge/quill_native_bridge_windows/lib/src/html_formatter.dart new file mode 100644 index 000000000..4af40133e --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_windows/lib/src/html_formatter.dart @@ -0,0 +1,116 @@ +// Used to convert a HTML to format that the Windows Clipboard expect. + +const _kStartBodyTag = ''; +const _kEndBodyTag = ''; + +const _kStartHtmlTag = ''; +const _kEndHtmlTag = ''; + +const _kStartFragmentComment = ''; +const _kEndFragmentComment = ''; + +/// Provide a header with additional information to the [html] +/// for the HTML to be suitable for storing in the Windows Clipboard. +/// Windows clipboard expect this additional information is set before +/// copying [html] to the clipboard. +/// +/// See [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format) +/// for more details. +String constructWindowsHtmlDescriptionHeaders(String html) { + final htmlBodyContent = _extractBodyContent(html); + + // TODO: Handle the case where the HTML already have those headers (not common) + + // Version `1.0` is supported on Windows 10 20H2 and newer versions. + // `StartSelection` and `EndSelection` are optional. + // See https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format#description-headers-and-offsets + const version = '1.0'; + + /// HTML template containing placeholders for invalid header values; will be replaced in a separate variable. + final invalidHeaderHtmlTemplate = ''' +Version:$version +StartHTML:0001 +EndHTML:0002 +StartFragment:0003 +EndFragment:0004 +$_kStartFragmentComment$htmlBodyContent$_kEndFragmentComment +'''; + + // Important: Should calculate offsets after adding the headers (StartHTML, EndHTML, etc.) + + // Windows expect those to be -1 if no context provided + // https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format#description-headers-and-offsets + + final startHtmlPos = invalidHeaderHtmlTemplate.indexOf(_kStartHtmlTag) + + _kStartHtmlTag.length; // Start After + final endHtmlPos = + invalidHeaderHtmlTemplate.indexOf(_kEndHtmlTag); // End before + + final startFragment = + invalidHeaderHtmlTemplate.indexOf(_kStartFragmentComment) + + _kStartFragmentComment.length; // Start after + final endFragment = invalidHeaderHtmlTemplate + .indexOf(_kEndFragmentComment); // End before + + // Important: Those invalid values should remain with the same length + // in the template as they used to calculate the offsets + // even if they have different values, otherwise will + // cause a bug as the offsets will be invalid. + return invalidHeaderHtmlTemplate + .replaceFirst('0001', _formatPosition(startHtmlPos)) + .replaceFirst('0002', _formatPosition(endHtmlPos)) + .replaceFirst('0003', _formatPosition(startFragment)) + .replaceFirst('0004', _formatPosition(endFragment)); +} + +/// Formats a given position to a 4-digit zero-padded string. +/// +/// This is necessary because Windows clipboard requires the positions to +/// be formatted in a specific way, using 4 digits, with leading zeros +/// if necessary. For example, the position `121` would be formatted as +/// `00121`. +/// +/// [position] The offset position (in bytes) to format. +/// +/// Returns: A string representing the formatted position. +/// +/// See [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format) +/// for more details. +String _formatPosition(int position) { + if (position == -1) { + return position.toString(); + } + return position.toString().padLeft(4, '0'); +} + +/// Extracts the content within ... tags from the provided [html]. +/// +/// If `` and `` tags are found, the content between them is returned. +/// If `` and `` tags are not present, the entire HTML string is returned, +/// trimmed of leading and trailing whitespace. +/// +/// Example: +/// ```dart +/// String html = 'Hello World'; +/// String bodyContent = _extractBodyContent(html); +/// print(bodyContent); // Output: 'Hello World' +/// ``` +/// +/// **Note**: +/// This operation is case-insensitive and will treat `` and `` as equivalent. +String _extractBodyContent(String html) { + final startBodyIndex = html.toLowerCase().indexOf(_kStartBodyTag); + final endBodyIndex = html.toLowerCase().indexOf(_kEndBodyTag); + + final bodyTagFound = startBodyIndex != -1 && endBodyIndex != -1; + if (bodyTagFound) { + // Extract the content inside HTML Content + final bodyContentStartIndex = startBodyIndex + _kStartBodyTag.length; + final bodyContent = + html.substring(bodyContentStartIndex, endBodyIndex).trim(); + return bodyContent; + } + + // No with found + return html.trim(); +} diff --git a/quill_native_bridge/quill_native_bridge_windows/test/html_cleaner_test.dart b/quill_native_bridge/quill_native_bridge_windows/test/html_cleaner_test.dart index d38082cc9..81140502e 100644 --- a/quill_native_bridge/quill_native_bridge_windows/test/html_cleaner_test.dart +++ b/quill_native_bridge/quill_native_bridge_windows/test/html_cleaner_test.dart @@ -26,7 +26,7 @@ EndFragment:0000000598 '''; final strippedHtml = - stripWin32HtmlDescription(windowsClipboardHtmlExample); + stripWindowsHtmlDescriptionHeaders(windowsClipboardHtmlExample); expect( strippedHtml, expectedHtml, @@ -44,7 +44,7 @@ EndFragment:0000000598 '''; - expect(stripWin32HtmlDescription(cleanHtml), equals(cleanHtml)); + expect(stripWindowsHtmlDescriptionHeaders(cleanHtml), equals(cleanHtml)); }); }); } diff --git a/quill_native_bridge/quill_native_bridge_windows/test/html_formatter_test.dart b/quill_native_bridge/quill_native_bridge_windows/test/html_formatter_test.dart new file mode 100644 index 000000000..6a4a817cb --- /dev/null +++ b/quill_native_bridge/quill_native_bridge_windows/test/html_formatter_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:quill_native_bridge_windows/src/html_formatter.dart'; + +void main() { + group('constructWindowsHtmlDescriptionHeaders', () { + test('should include windows descirption headers', () { + const htmlInput = + 'This is normal. This is bold. This is bold italic. This is italic.'; + const expectedWindowsHtml = ''' +Version:1.0 +StartHTML:0082 +EndHTML:0220 +StartFragment:0102 +EndFragment:0202 +This is normal. This is bold. This is bold italic. This is italic. +'''; + expect( + constructWindowsHtmlDescriptionHeaders(htmlInput), + expectedWindowsHtml, + ); + }); + }); +} From b30923ff06cd47695e9a009870eea42192610714 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 29 Sep 2024 11:38:14 +0300 Subject: [PATCH 69/90] refactor: extract supported platforms into Set for isSupported() --- .../lib/quill_native_bridge_android.dart | 24 ++++++------------- .../lib/quill_native_bridge_ios.dart | 24 +++++++------------ .../lib/quill_native_bridge_linux.dart | 24 +++++-------------- .../lib/quill_native_bridge_macos.dart | 24 +++++-------------- .../lib/quill_native_bridge_windows.dart | 23 ++++-------------- 5 files changed, 31 insertions(+), 88 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart index dbf196036..9795d9380 100644 --- a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart +++ b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart @@ -36,23 +36,13 @@ class QuillNativeBridgeAndroid extends QuillNativeBridgePlatform { } @override - Future isSupported(QuillNativeBridgeFeature feature) async { - switch (feature) { - case QuillNativeBridgeFeature.isIOSSimulator: - return false; - case QuillNativeBridgeFeature.getClipboardHtml: - case QuillNativeBridgeFeature.copyHtmlToClipboard: - case QuillNativeBridgeFeature.copyImageToClipboard: - case QuillNativeBridgeFeature.getClipboardImage: - case QuillNativeBridgeFeature.getClipboardGif: - return true; - // Without this default check, adding new item to the enum will be a breaking change - default: - throw UnimplementedError( - 'Checking if `${feature.name}` is supported on Android is not covered.', - ); - } - } + Future isSupported(QuillNativeBridgeFeature feature) async => { + QuillNativeBridgeFeature.getClipboardHtml, + QuillNativeBridgeFeature.copyHtmlToClipboard, + QuillNativeBridgeFeature.copyImageToClipboard, + QuillNativeBridgeFeature.getClipboardImage, + QuillNativeBridgeFeature.getClipboardGif, + }.contains(feature); @override Future getClipboardHtml() async => _hostApi.getClipboardHtml(); diff --git a/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart b/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart index a5fc7364c..d98eb5382 100644 --- a/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart +++ b/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart @@ -35,22 +35,14 @@ class QuillNativeBridgeIos extends QuillNativeBridgePlatform { } @override - Future isSupported(QuillNativeBridgeFeature feature) async { - switch (feature) { - case QuillNativeBridgeFeature.isIOSSimulator: - case QuillNativeBridgeFeature.getClipboardHtml: - case QuillNativeBridgeFeature.copyHtmlToClipboard: - case QuillNativeBridgeFeature.copyImageToClipboard: - case QuillNativeBridgeFeature.getClipboardImage: - case QuillNativeBridgeFeature.getClipboardGif: - return true; - // Without this default check, adding new item to the enum will be a breaking change - default: - throw UnimplementedError( - 'Checking if `${feature.name}` is supported on iOS is not covered.', - ); - } - } + Future isSupported(QuillNativeBridgeFeature feature) async => { + QuillNativeBridgeFeature.isIOSSimulator, + QuillNativeBridgeFeature.getClipboardHtml, + QuillNativeBridgeFeature.copyHtmlToClipboard, + QuillNativeBridgeFeature.copyImageToClipboard, + QuillNativeBridgeFeature.getClipboardImage, + QuillNativeBridgeFeature.getClipboardGif, + }.contains(feature); @override Future isIOSSimulator() => _hostApi.isIosSimulator(); diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart b/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart index ca34ae16f..0ddc24722 100644 --- a/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart +++ b/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart @@ -33,24 +33,12 @@ class QuillNativeBridgeLinux extends QuillNativeBridgePlatform { } @override - Future isSupported(QuillNativeBridgeFeature feature) async { - switch (feature) { - case QuillNativeBridgeFeature.isIOSSimulator: - return false; - case QuillNativeBridgeFeature.getClipboardHtml: - case QuillNativeBridgeFeature.copyHtmlToClipboard: - case QuillNativeBridgeFeature.copyImageToClipboard: - case QuillNativeBridgeFeature.getClipboardImage: - return true; - case QuillNativeBridgeFeature.getClipboardGif: - return false; - // Without this default check, adding new item to the enum will be a breaking change - default: - throw UnimplementedError( - 'Checking if `${feature.name}` is supported on Linux is not covered.', - ); - } - } + Future isSupported(QuillNativeBridgeFeature feature) async => { + QuillNativeBridgeFeature.getClipboardHtml, + QuillNativeBridgeFeature.copyHtmlToClipboard, + QuillNativeBridgeFeature.copyImageToClipboard, + QuillNativeBridgeFeature.getClipboardImage + }.contains(feature); // TODO: Improve error handling diff --git a/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart b/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart index c48d98169..6d522295f 100644 --- a/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart +++ b/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart @@ -35,24 +35,12 @@ class QuillNativeBridgeMacOS extends QuillNativeBridgePlatform { } @override - Future isSupported(QuillNativeBridgeFeature feature) async { - switch (feature) { - case QuillNativeBridgeFeature.isIOSSimulator: - return false; - case QuillNativeBridgeFeature.getClipboardHtml: - case QuillNativeBridgeFeature.copyHtmlToClipboard: - case QuillNativeBridgeFeature.copyImageToClipboard: - case QuillNativeBridgeFeature.getClipboardImage: - return true; - case QuillNativeBridgeFeature.getClipboardGif: - return false; - // Without this default check, adding new item to the enum will be a breaking change - default: - throw UnimplementedError( - 'Checking if `${feature.name}` is supported on macOS is not covered.', - ); - } - } + Future isSupported(QuillNativeBridgeFeature feature) async => { + QuillNativeBridgeFeature.getClipboardHtml, + QuillNativeBridgeFeature.copyHtmlToClipboard, + QuillNativeBridgeFeature.copyImageToClipboard, + QuillNativeBridgeFeature.getClipboardImage + }.contains(feature); @override Future isIOSSimulator() => throw UnsupportedError( diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index 00b82be77..2642951a2 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -32,25 +32,10 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { } @override - Future isSupported(QuillNativeBridgeFeature feature) async { - // TODO: Extract supported features in Set (do the same for other packages) - switch (feature) { - case QuillNativeBridgeFeature.isIOSSimulator: - return false; - case QuillNativeBridgeFeature.getClipboardHtml: - case QuillNativeBridgeFeature.copyHtmlToClipboard: - return true; - case QuillNativeBridgeFeature.copyImageToClipboard: - case QuillNativeBridgeFeature.getClipboardImage: - case QuillNativeBridgeFeature.getClipboardGif: - return false; - // Without this default check, adding new item to the enum will be a breaking change - default: - throw UnimplementedError( - 'Checking if `${feature.name}` is supported on Windows is not covered.', - ); - } - } + Future isSupported(QuillNativeBridgeFeature feature) async => { + QuillNativeBridgeFeature.getClipboardHtml, + QuillNativeBridgeFeature.copyHtmlToClipboard, + }.contains(feature); // TODO: Cleanup this code here From 67943513fdc326a21c11421682ec1817c53bb640 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 29 Sep 2024 11:53:31 +0300 Subject: [PATCH 70/90] chore(windows): add error code in errors using GetLastError() --- .../lib/quill_native_bridge_windows.dart | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index 2642951a2..b16be6030 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -52,7 +52,10 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { @override Future getClipboardHtml() async { if (OpenClipboard(NULL) == FALSE) { - assert(false, 'Unknown error while opening the clipboard.'); + assert( + false, + 'Unknown error while opening the clipboard. Error code: ${GetLastError()}', + ); return null; } @@ -70,7 +73,10 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { final clipboardDataHandle = GetClipboardData(htmlFormatId); if (clipboardDataHandle == NULL) { - assert(false, 'Failed to get clipboard data.'); + assert( + false, + 'Failed to get clipboard data. Error code: ${GetLastError()}', + ); return null; } @@ -104,7 +110,10 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { @override Future copyHtmlToClipboard(String html) async { if (OpenClipboard(NULL) == FALSE) { - assert(false, 'Unknown error while opening the clipboard.'); + assert( + false, + 'Unknown error while opening the clipboard. Error code: ${GetLastError()}', + ); return; } @@ -113,14 +122,20 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { try { if (EmptyClipboard() == FALSE) { - assert(false, 'Failed to empty the clipboard.'); + assert( + false, + 'Failed to empty the clipboard. Error code: ${GetLastError()}', + ); return; } final htmlFormatId = cfHtml; if (htmlFormatId == null) { - assert(false, 'Failed to register clipboard HTML format.'); + assert( + false, + 'Failed to register clipboard HTML format. Error code: ${GetLastError()}', + ); return; } @@ -130,7 +145,10 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { final globalMemoryHandle = GlobalAlloc(GLOBAL_ALLOC_FLAGS.GMEM_MOVEABLE, htmlSize); if (globalMemoryHandle == nullptr) { - assert(false, 'Failed to allocate memory for the clipboard content.'); + assert( + false, + 'Failed to allocate memory for the clipboard content. Error code: ${GetLastError()}', + ); return; } From 9c7ec6d79f01f7646c16d8c013b98dbcb7b03cf0 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 29 Sep 2024 13:07:14 +0300 Subject: [PATCH 71/90] chore(windows): avoid passing the locked memory pointer to SetClipboardData() in copyHtmlToClipboard() --- .../lib/quill_native_bridge_windows.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index b16be6030..c470e9f9f 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -142,9 +142,9 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { final unitSize = sizeOf(); final htmlSize = (htmlPointer.length + 1) * unitSize; - final globalMemoryHandle = + final clipboardMemoryHandle = GlobalAlloc(GLOBAL_ALLOC_FLAGS.GMEM_MOVEABLE, htmlSize); - if (globalMemoryHandle == nullptr) { + if (clipboardMemoryHandle == nullptr) { assert( false, 'Failed to allocate memory for the clipboard content. Error code: ${GetLastError()}', @@ -152,9 +152,9 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { return; } - final lockedMemoryPointer = GlobalLock(globalMemoryHandle); + final lockedMemoryPointer = GlobalLock(clipboardMemoryHandle); if (lockedMemoryPointer == nullptr) { - GlobalFree(globalMemoryHandle); + GlobalFree(clipboardMemoryHandle); assert( false, 'Failed to lock global memory. Error code: ${GetLastError()}', @@ -170,6 +170,8 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { for (var i = 0; i < htmlPointer.length; i++) { targetMemoryPointer[i] = (sourcePointer + i).value; } + + // Add a null terminator for HTML (necessary for proper string handling) (targetMemoryPointer + htmlPointer.length).value = NULL; // TODO: Handle errors here, check Win32 docs regarding GlobalUnlock() and SetClipboardData() @@ -178,7 +180,8 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { // According to Windows docs in https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclipboarddata#parameters // Should not unlock when SetClipboardData() success - if (SetClipboardData(htmlFormatId, lockedMemoryPointer.address) == NULL) { + if (SetClipboardData(htmlFormatId, clipboardMemoryHandle.address) == + NULL) { GlobalUnlock(lockedMemoryPointer); assert( false, From f69646c73a23f5612980ead27b50972ff63afe20 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 29 Sep 2024 16:43:33 +0300 Subject: [PATCH 72/90] docs(readme): document android platform configuration in quill_native_bridge --- .../quill_native_bridge/README.md | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/quill_native_bridge/quill_native_bridge/README.md b/quill_native_bridge/quill_native_bridge/README.md index 231e3b995..f08b2ce98 100644 --- a/quill_native_bridge/quill_native_bridge/README.md +++ b/quill_native_bridge/quill_native_bridge/README.md @@ -14,3 +14,44 @@ An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) | **getClipboardImage** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | **getClipboardGif** | ✅ | ✅ | âšĒ | âšĒ | âšĒ | âšĒ | +## 🔧 Platform Configuration + +To support copying images to the clipboard to be accessed by other apps, you need to configure your Android project. +If not set up, a warning will appear in the log during debug mode only +if `copyImageToClipboard` was called without configuring the Android project. + +> **IMPORTANT** +> This is only required on **Android** platform for this feature. +> Should be able to use other features on **Android** if `copyImageToClipboard` is not being used. + +**1. Update `AndroidManifest.xml`** + +Open `your_project/android/app/src/main/AndroidManifest.xml` and add the following inside the `` tag: + +```xml + + + ... + + + + ... + + +``` + +**2. Create `file_paths.xml`** + +Create the file `your_project/android/app/src/main/res/xml/file_paths.xml` with the following content: + +```xml + + + +``` \ No newline at end of file From 1c6e7b8d7b60fa0e9166f92a485fec317678e12f Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 29 Sep 2024 17:01:48 +0300 Subject: [PATCH 73/90] fix(windows): (WIP) GlobalUnlock before SetClipboardData and GlobalFree on SetClipboardData failure --- .../lib/quill_native_bridge_windows.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index c470e9f9f..9509c0da6 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -174,15 +174,18 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { // Add a null terminator for HTML (necessary for proper string handling) (targetMemoryPointer + htmlPointer.length).value = NULL; - // TODO: Handle errors here, check Win32 docs regarding GlobalUnlock() and SetClipboardData() - // to see any potential issues - // According to Windows docs in https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclipboarddata#parameters - // Should not unlock when SetClipboardData() success + // Should not call GlobalFree() when SetClipboardData() success + // as the Windows clipboard takes ownership of the memory. + + // TODO: Should we unlock clipboardMemoryHandle (GlobalAlloc) instead of lockedMemoryPointer (GlobalLock)? + // According to https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalunlock + // It's likely that clipboardMemoryHandle what needs to be unlocked + GlobalUnlock(lockedMemoryPointer); if (SetClipboardData(htmlFormatId, clipboardMemoryHandle.address) == NULL) { - GlobalUnlock(lockedMemoryPointer); + GlobalFree(clipboardMemoryHandle); assert( false, 'Failed to set the clipboard data: ${GetLastError()}', From 781de0ded1175d14e4e20191fd80014748d644b2 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 29 Sep 2024 17:21:32 +0300 Subject: [PATCH 74/90] docs(readme): slighly improve platform configuration docs in quill_native_bridge --- quill_native_bridge/quill_native_bridge/README.md | 12 ++++++++---- .../lib/quill_native_bridge_android.dart | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge/README.md b/quill_native_bridge/quill_native_bridge/README.md index f08b2ce98..9b34b64e0 100644 --- a/quill_native_bridge/quill_native_bridge/README.md +++ b/quill_native_bridge/quill_native_bridge/README.md @@ -2,7 +2,8 @@ An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) package to access platform-specific APIs. -> [!NOTE] +> **NOTE** +> > **Internal Use Only**: Exclusively for `flutter_quill`. Breaking changes may occur. | Feature | iOS | Android | macOS | Windows | Linux | Web | @@ -16,13 +17,16 @@ An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) ## 🔧 Platform Configuration -To support copying images to the clipboard to be accessed by other apps, you need to configure your Android project. +To support copying images to the system clipboard on **Android**. A platform configuration setup is required. If not set up, a warning will appear in the log during debug mode only if `copyImageToClipboard` was called without configuring the Android project. +An exception with less details will be thrown in production mode. > **IMPORTANT** -> This is only required on **Android** platform for this feature. -> Should be able to use other features on **Android** if `copyImageToClipboard` is not being used. +> +> This configuration is required on **Android** platform for using `copyImageToClipboard`. +> Other features on Android will work without it if this method isn't used. +> For more information, refer to the [Android FileProvider documentation](https://developer.android.com/reference/androidx/core/content/FileProvider). **1. Update `AndroidManifest.xml`** diff --git a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart index 9795d9380..c96330f32 100644 --- a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart +++ b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart @@ -71,6 +71,8 @@ class QuillNativeBridgeAndroid extends QuillNativeBridgePlatform { try { await _hostApi.copyImageToClipboard(imageBytes); } on PlatformException catch (e) { + // TODO: Update the link, issue and related info if this plugin + // moved outside of flutter-quill repo if (kDebugMode && e.code == 'ANDROID_MANIFEST_NOT_CONFIGURED') { debugPrint( 'It looks like your AndroidManifest.xml is not configured properly ' From ec6751fe575e471dd10e05b209b53f0f9b9dfac7 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sun, 29 Sep 2024 17:51:37 +0300 Subject: [PATCH 75/90] fix(windows): GlobalUnlock() the handle instead of pointer before calling GlobalUnlock() --- .../lib/quill_native_bridge_windows.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart index 9509c0da6..ac6dc1bb3 100644 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart @@ -178,10 +178,7 @@ class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { // Should not call GlobalFree() when SetClipboardData() success // as the Windows clipboard takes ownership of the memory. - // TODO: Should we unlock clipboardMemoryHandle (GlobalAlloc) instead of lockedMemoryPointer (GlobalLock)? - // According to https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalunlock - // It's likely that clipboardMemoryHandle what needs to be unlocked - GlobalUnlock(lockedMemoryPointer); + GlobalUnlock(clipboardMemoryHandle); if (SetClipboardData(htmlFormatId, clipboardMemoryHandle.address) == NULL) { From 3c14070923bd49a9ec997b3e19224fffd3920dc5 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 30 Sep 2024 01:33:05 +0300 Subject: [PATCH 76/90] chore(android): add reference to Flutter #63533 --- .../quill_native_bridge_android/android/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/quill_native_bridge/quill_native_bridge_android/android/build.gradle b/quill_native_bridge/quill_native_bridge_android/android/build.gradle index e57ffa7e1..6232173a3 100644 --- a/quill_native_bridge/quill_native_bridge_android/android/build.gradle +++ b/quill_native_bridge/quill_native_bridge_android/android/build.gradle @@ -29,6 +29,9 @@ android { namespace = "dev.flutterquill.quill_native_bridge" } + // Using outdated compileSdk can prevent apps + // from build using newer API + // https://github.com/flutter/flutter/issues/63533 compileSdk = 34 compileOptions { From 299a976c99d68fafb53ef920ab1fc1328a488387 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 30 Sep 2024 13:00:12 +0300 Subject: [PATCH 77/90] chore(android): define package in GeneratedMessages.kt, update related imports --- .../QuillNativeBridgeImpl.kt | 2 +- .../QuillNativeBridgePlugin.kt | 2 +- .../clipboard/ClipboardReadImageHandler.kt | 2 +- .../clipboard/ClipboardRichTextHandler.kt | 2 +- .../clipboard/ClipboardWriteImageHandler.kt | 2 +- .../generated/GeneratedMessages.kt | 3 +- .../lib/src/messages.g.dart | 56 ++++++++++--------- .../pigeons/messages.dart | 3 + .../quill_native_bridge_android/pubspec.yaml | 2 +- 9 files changed, 42 insertions(+), 32 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgeImpl.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgeImpl.kt index bc38548a0..c8c6abf54 100644 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgeImpl.kt +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgeImpl.kt @@ -1,10 +1,10 @@ package dev.flutterquill.quill_native_bridge -import QuillNativeBridgeApi import android.content.Context import dev.flutterquill.quill_native_bridge.clipboard.ClipboardReadImageHandler import dev.flutterquill.quill_native_bridge.clipboard.ClipboardRichTextHandler import dev.flutterquill.quill_native_bridge.clipboard.ClipboardWriteImageHandler +import dev.flutterquill.quill_native_bridge.generated.QuillNativeBridgeApi class QuillNativeBridgeImpl(private val context: Context) : QuillNativeBridgeApi { override fun getClipboardHtml(): String? = ClipboardRichTextHandler.getClipboardHtml(context) diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt index 4e2f95063..62249993e 100644 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt @@ -1,6 +1,6 @@ package dev.flutterquill.quill_native_bridge -import QuillNativeBridgeApi +import dev.flutterquill.quill_native_bridge.generated.QuillNativeBridgeApi import io.flutter.embedding.engine.plugins.FlutterPlugin class QuillNativeBridgePlugin : FlutterPlugin { diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt index c00b18990..b2b805dc4 100644 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt @@ -1,6 +1,5 @@ package dev.flutterquill.quill_native_bridge.clipboard -import FlutterError import android.content.ClipData import android.content.ClipboardManager import android.content.Context @@ -10,6 +9,7 @@ import android.graphics.ImageDecoder import android.net.Uri import android.os.Build import androidx.core.graphics.decodeBitmap +import dev.flutterquill.quill_native_bridge.generated.FlutterError import java.io.ByteArrayOutputStream import java.io.FileNotFoundException diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt index 9a2917fbe..a7034cebc 100644 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt @@ -1,10 +1,10 @@ package dev.flutterquill.quill_native_bridge.clipboard -import FlutterError import android.content.ClipData import android.content.ClipDescription import android.content.ClipboardManager import android.content.Context +import dev.flutterquill.quill_native_bridge.generated.FlutterError object ClipboardRichTextHandler { fun getClipboardHtml(context: Context): String? { diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt index 43e8dc27a..b1574c0ca 100644 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt @@ -1,6 +1,5 @@ package dev.flutterquill.quill_native_bridge.clipboard -import FlutterError import android.content.ClipData import android.content.ClipboardManager import android.content.Context @@ -9,6 +8,7 @@ import android.graphics.BitmapFactory import android.graphics.ImageDecoder import android.os.Build import androidx.core.content.FileProvider +import dev.flutterquill.quill_native_bridge.generated.FlutterError import java.io.File object ClipboardWriteImageHandler { diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt index 8a6c351cc..ee6cad372 100644 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt +++ b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt @@ -1,7 +1,8 @@ -// Autogenerated from Pigeon (v22.4.0), do not edit directly. +// Autogenerated from Pigeon (v22.4.1), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") +package dev.flutterquill.quill_native_bridge.generated import android.util.Log import io.flutter.plugin.common.BasicMessageChannel diff --git a/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart index e6c0d69c6..ba9e7d5c6 100644 --- a/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart +++ b/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v22.4.0), do not edit directly. +// Autogenerated from Pigeon (v22.4.1), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -15,19 +15,35 @@ PlatformException _createConnectionError(String channelName) { ); } + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } } class QuillNativeBridgeApi { /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - QuillNativeBridgeApi( - {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + QuillNativeBridgeApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -35,10 +51,8 @@ class QuillNativeBridgeApi { final String pigeonVar_messageChannelSuffix; Future getClipboardHtml() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -59,10 +73,8 @@ class QuillNativeBridgeApi { } Future copyHtmlToClipboard(String html) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -83,10 +95,8 @@ class QuillNativeBridgeApi { } Future getClipboardImage() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -107,10 +117,8 @@ class QuillNativeBridgeApi { } Future copyImageToClipboard(Uint8List imageBytes) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -131,10 +139,8 @@ class QuillNativeBridgeApi { } Future getClipboardGif() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, diff --git a/quill_native_bridge/quill_native_bridge_android/pigeons/messages.dart b/quill_native_bridge/quill_native_bridge_android/pigeons/messages.dart index b1a646508..cd84dedf0 100644 --- a/quill_native_bridge/quill_native_bridge_android/pigeons/messages.dart +++ b/quill_native_bridge/quill_native_bridge_android/pigeons/messages.dart @@ -6,6 +6,9 @@ import 'package:pigeon/pigeon.dart'; // Kotlin conventions: https://kotlinlang.org/docs/coding-conventions.html#source-file-names kotlinOut: 'android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt', + kotlinOptions: KotlinOptions( + package: 'dev.flutterquill.quill_native_bridge.generated', + ), dartPackageName: 'quill_native_bridge_android', )) @HostApi() diff --git a/quill_native_bridge/quill_native_bridge_android/pubspec.yaml b/quill_native_bridge/quill_native_bridge_android/pubspec.yaml index d3b568946..0a3419388 100644 --- a/quill_native_bridge/quill_native_bridge_android/pubspec.yaml +++ b/quill_native_bridge/quill_native_bridge_android/pubspec.yaml @@ -19,7 +19,7 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^4.0.0 - pigeon: ^22.4.0 + pigeon: ^22.4.1 flutter: plugin: From f44a802bc9985e5a7d80e8f020cf35c30222f7f2 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 30 Sep 2024 13:20:45 +0300 Subject: [PATCH 78/90] chore(android): ignore analysis warning for the generated code messages.g.dart --- .../lib/src/messages.g.dart | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart index ba9e7d5c6..c7327cb76 100644 --- a/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart +++ b/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart @@ -15,13 +15,14 @@ PlatformException _createConnectionError(String channelName) { ); } - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is int) { buffer.putUint8(4); + // TODO: Workaround to https://github.com/flutter/packages/pull/7735 + // ignore: cascade_invocations buffer.putInt64(value); } else { super.writeValue(buffer, value); @@ -41,9 +42,11 @@ class QuillNativeBridgeApi { /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - QuillNativeBridgeApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + QuillNativeBridgeApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -51,8 +54,10 @@ class QuillNativeBridgeApi { final String pigeonVar_messageChannelSuffix; Future getClipboardHtml() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -73,8 +78,10 @@ class QuillNativeBridgeApi { } Future copyHtmlToClipboard(String html) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -95,8 +102,10 @@ class QuillNativeBridgeApi { } Future getClipboardImage() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -117,8 +126,10 @@ class QuillNativeBridgeApi { } Future copyImageToClipboard(Uint8List imageBytes) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -139,8 +150,10 @@ class QuillNativeBridgeApi { } Future getClipboardGif() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, From d5344dab24914a71f917b54d43c20fb6b31b73df Mon Sep 17 00:00:00 2001 From: Ellet Date: Tue, 1 Oct 2024 15:53:18 +0300 Subject: [PATCH 79/90] feat: (WIP) add initial support for getClipboardFiles() --- example/macos/Podfile.lock | 23 ++----- .../clipboard/clipboard_service.dart | 1 + .../clipboard/default_clipboard_service.dart | 28 +++++++- .../quill_native_bridge/README.md | 1 + .../quill_native_bridge/example/lib/main.dart | 27 ++++++++ .../lib/quill_native_bridge.dart | 6 ++ .../lib/quill_native_bridge_macos.dart | 6 +- .../lib/src/messages.g.dart | 64 ++++++++++++------- .../macos/Classes/Messages.g.swift | 14 ++++ .../macos/Classes/QuillNativeBridgeImpl.swift | 7 ++ .../pigeons/messages.dart | 3 + ...uill_native_bridge_platform_interface.dart | 4 ++ .../lib/src/platform_feature.dart | 13 ++-- .../quill_native_bridge_method_channel.dart | 8 +++ .../test/quill_native_bridge_test.dart | 11 ++++ 15 files changed, 164 insertions(+), 52 deletions(-) diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 2d04e0a53..d9fe20ac9 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -5,20 +5,16 @@ PODS: - FlutterMacOS - file_selector_macos (0.0.1): - FlutterMacOS - - flutter_inappwebview_macos (0.0.1): - - FlutterMacOS - - OrderedSet (~> 5.0) - FlutterMacOS (1.0.0) - gal (1.0.0): - Flutter - FlutterMacOS - irondash_engine_context (0.0.1): - FlutterMacOS - - OrderedSet (5.0.0) - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - quill_native_bridge (0.0.1): + - quill_native_bridge_macos (0.0.1): - FlutterMacOS - share_plus (0.0.1): - FlutterMacOS @@ -37,22 +33,17 @@ DEPENDENCIES: - desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`) - irondash_engine_context (from `Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - - quill_native_bridge (from `Flutter/ephemeral/.symlinks/plugins/quill_native_bridge/macos`) + - quill_native_bridge_macos (from `Flutter/ephemeral/.symlinks/plugins/quill_native_bridge_macos/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`) - super_native_extensions (from `Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`) -SPEC REPOS: - trunk: - - OrderedSet - EXTERNAL SOURCES: desktop_drop: :path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos @@ -60,8 +51,6 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos file_selector_macos: :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos - flutter_inappwebview_macos: - :path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos FlutterMacOS: :path: Flutter/ephemeral gal: @@ -70,8 +59,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin - quill_native_bridge: - :path: Flutter/ephemeral/.symlinks/plugins/quill_native_bridge/macos + quill_native_bridge_macos: + :path: Flutter/ephemeral/.symlinks/plugins/quill_native_bridge_macos/macos share_plus: :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos sqflite: @@ -87,13 +76,11 @@ SPEC CHECKSUMS: desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720 file_selector_macos: 54fdab7caa3ac3fc43c9fac4d7d8d231277f8cf2 - flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1 irondash_engine_context: da62996ee25616d2f01bbeb85dc115d813359478 - OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - quill_native_bridge: 1a3a4bfab7cbe4ed0232a17d8aae201a3ce6d302 + quill_native_bridge_macos: f90985c5269ac7ba84d933605b463d96e5f544fe share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec super_native_extensions: 85efee3a7495b46b04befcfc86ed12069264ebf3 diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart index 67a20714d..3f18a409a 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart @@ -21,6 +21,7 @@ abstract class ClipboardService { /// Return Gif from the Clipboard. Future getGifFile(); + // TODO: The `Clipboard` in `copyImageToClipboard` can be redundant /// Copy [imageBytes] to the system clipboard to paste on other apps. Future copyImageToClipboard(Uint8List imageBytes); diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart index fe2873371..bec75d701 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart @@ -1,4 +1,6 @@ -import 'package:flutter/services.dart' show Uint8List; +import 'dart:io' as io; + +import 'package:flutter/foundation.dart'; import 'package:meta/meta.dart' show experimental; import 'package:quill_native_bridge/quill_native_bridge.dart' show QuillNativeBridge, QuillNativeBridgeFeature; @@ -45,13 +47,33 @@ class DefaultClipboardService extends ClipboardService { return QuillNativeBridge.getClipboardGif(); } + Future _getClipboardFile({required String fileExtension}) async { + if (kIsWeb) { + // TODO: Can't read file with dart:io on the Web + return null; + } + final filePaths = await QuillNativeBridge.getClipboardFiles(); + final filePath = filePaths.firstWhere( + (filePath) => filePath.endsWith('.$fileExtension'), + orElse: () => '', + ); + if (filePath.isEmpty) { + // Could not find an item + return null; + } + final fileText = await io.File(filePath).readAsString(); + return fileText; + } + @override Future getHtmlFile() async { - return null; + final htmlFileText = await _getClipboardFile(fileExtension: 'html'); + return htmlFileText; } @override Future getMarkdownFile() async { - return null; + final htmlFileText = await _getClipboardFile(fileExtension: 'md'); + return htmlFileText; } } diff --git a/quill_native_bridge/quill_native_bridge/README.md b/quill_native_bridge/quill_native_bridge/README.md index 9b34b64e0..7b0a7fd7b 100644 --- a/quill_native_bridge/quill_native_bridge/README.md +++ b/quill_native_bridge/quill_native_bridge/README.md @@ -14,6 +14,7 @@ An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) | **copyImageToClipboard** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | **getClipboardImage** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | **getClipboardGif** | ✅ | ✅ | âšĒ | âšĒ | âšĒ | âšĒ | +| **getClipboardFiles** | âšĒ | âšĒ | ✅ | ❌ | ❌ | ❌ | ## 🔧 Platform Configuration diff --git a/quill_native_bridge/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/quill_native_bridge/example/lib/main.dart index ebe0ef1a2..879f0c5be 100644 --- a/quill_native_bridge/quill_native_bridge/example/lib/main.dart +++ b/quill_native_bridge/quill_native_bridge/example/lib/main.dart @@ -88,6 +88,14 @@ class Buttons extends StatelessWidget { label: const Text('Retrieve Gif from Clipboard'), icon: const Icon(Icons.gif), ), + ElevatedButton.icon( + onPressed: () => _onButtonClick( + QuillNativeBridgeFeature.getClipboardFiles, + context: context, + ), + label: const Text('Retrieve Files from Clipboard'), + icon: const Icon(Icons.file_open), + ), ], ); } @@ -239,6 +247,25 @@ class Buttons extends StatelessWidget { ), ); break; + case QuillNativeBridgeFeature.getClipboardFiles: + if (isFeatureUnsupported) { + scaffoldMessenger.showText( + isFeatureWebUnsupported + ? 'Retrieving a gif from the clipboard is currently not supported on web.' + : 'Retrieving a gif from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', + ); + return; + } + final files = await QuillNativeBridge.getClipboardFiles(); + if (files.isEmpty) { + scaffoldMessenger.showText('There are no files on the clipboard.'); + return; + } + scaffoldMessenger.showText( + 'Files from the clipboard: ${files.toString()}', + ); + debugPrint('Files from the clipboard: $files'); + break; } } } diff --git a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart index 0c6ed0d49..ae98685dd 100644 --- a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart @@ -94,4 +94,10 @@ class QuillNativeBridge { /// /// Currently only supports **Android**, **iOS**. static Future getClipboardGif() => _platform.getClipboardGif(); + + /// Return the file paths from the Clipboard. + /// + /// Currently only supports **macOS**. + static Future> getClipboardFiles() => + _platform.getClipboardFiles(); } diff --git a/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart b/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart index 6d522295f..04c816516 100644 --- a/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart +++ b/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart @@ -39,7 +39,8 @@ class QuillNativeBridgeMacOS extends QuillNativeBridgePlatform { QuillNativeBridgeFeature.getClipboardHtml, QuillNativeBridgeFeature.copyHtmlToClipboard, QuillNativeBridgeFeature.copyImageToClipboard, - QuillNativeBridgeFeature.getClipboardImage + QuillNativeBridgeFeature.getClipboardImage, + QuillNativeBridgeFeature.getClipboardFiles, }.contains(feature); @override @@ -63,4 +64,7 @@ class QuillNativeBridgeMacOS extends QuillNativeBridgePlatform { @override Future getClipboardGif() => _hostApi.getClipboardGif(); + + @override + Future> getClipboardFiles() => _hostApi.getClipboardFiles(); } diff --git a/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart index 7cb36e3bd..f2e6da83b 100644 --- a/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart +++ b/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart @@ -15,6 +15,7 @@ PlatformException _createConnectionError(String channelName) { ); } + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); } @@ -23,11 +24,9 @@ class QuillNativeBridgeApi { /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - QuillNativeBridgeApi( - {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + QuillNativeBridgeApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -35,10 +34,8 @@ class QuillNativeBridgeApi { final String pigeonVar_messageChannelSuffix; Future getClipboardHtml() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -59,10 +56,8 @@ class QuillNativeBridgeApi { } Future copyHtmlToClipboard(String html) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -83,10 +78,8 @@ class QuillNativeBridgeApi { } Future getClipboardImage() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -107,10 +100,8 @@ class QuillNativeBridgeApi { } Future copyImageToClipboard(Uint8List imageBytes) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -131,10 +122,8 @@ class QuillNativeBridgeApi { } Future getClipboardGif() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -153,4 +142,31 @@ class QuillNativeBridgeApi { return (pigeonVar_replyList[0] as Uint8List?); } } + + Future> getClipboardFiles() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardFiles$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as List?)!.cast(); + } + } } diff --git a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/Messages.g.swift b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/Messages.g.swift index 462914b1c..1bf9c3b96 100644 --- a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/Messages.g.swift +++ b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/Messages.g.swift @@ -91,6 +91,7 @@ protocol QuillNativeBridgeApi { func getClipboardImage() throws -> FlutterStandardTypedData? func copyImageToClipboard(imageBytes: FlutterStandardTypedData) throws func getClipboardGif() throws -> FlutterStandardTypedData? + func getClipboardFiles() throws -> [String] } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -168,5 +169,18 @@ class QuillNativeBridgeApiSetup { } else { getClipboardGifChannel.setMessageHandler(nil) } + let getClipboardFilesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardFiles\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getClipboardFilesChannel.setMessageHandler { _, reply in + do { + let result = try api.getClipboardFiles() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getClipboardFilesChannel.setMessageHandler(nil) + } } } diff --git a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgeImpl.swift b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgeImpl.swift index 3b9fad280..7593168a3 100644 --- a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgeImpl.swift +++ b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgeImpl.swift @@ -47,4 +47,11 @@ class QuillNativeBridgeImpl: QuillNativeBridgeApi { let availableTypes = NSPasteboard.general.types throw PigeonError(code: "GIF_UNSUPPORTED", message: "Gif image is not supported on macOS. Available types: \(String(describing: availableTypes))", details: nil) } + + func getClipboardFiles() throws -> [String] { + guard let urlList = NSPasteboard.general.readObjects(forClasses: [NSURL.self], options: nil) as? [NSURL] else { + return [] + } + return urlList.compactMap { url in url.path } + } } diff --git a/quill_native_bridge/quill_native_bridge_macos/pigeons/messages.dart b/quill_native_bridge/quill_native_bridge_macos/pigeons/messages.dart index 1a01a2176..3048b62db 100644 --- a/quill_native_bridge/quill_native_bridge_macos/pigeons/messages.dart +++ b/quill_native_bridge/quill_native_bridge_macos/pigeons/messages.dart @@ -15,4 +15,7 @@ abstract class QuillNativeBridgeApi { Uint8List? getClipboardImage(); void copyImageToClipboard(Uint8List imageBytes); Uint8List? getClipboardGif(); + + // File + List getClipboardFiles(); } diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart index 7316bb618..bedf7481c 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart @@ -82,4 +82,8 @@ abstract class QuillNativeBridgePlatform extends PlatformInterface { /// Return the copied gif from the Clipboard. Future getClipboardGif() => throw UnimplementedError('getClipboardGif() has not been implemented.'); + + /// Return the file paths from the Clipboard. + Future> getClipboardFiles() => + throw UnimplementedError('getClipboardFiles() has not been implemented.'); } diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart index 120c3636b..952bf8d0a 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart @@ -1,11 +1,12 @@ /// The platform features provided by the plugin enum QuillNativeBridgeFeature { - isIOSSimulator(), - getClipboardHtml(), - copyHtmlToClipboard(), - copyImageToClipboard(), - getClipboardImage(), - getClipboardGif(); + isIOSSimulator, + getClipboardHtml, + copyHtmlToClipboard, + copyImageToClipboard, + getClipboardImage, + getClipboardGif, + getClipboardFiles; const QuillNativeBridgeFeature(); diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart index 73b116b2a..011fcec1a 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart @@ -107,4 +107,12 @@ class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { ); return gifBytes; } + + @override + Future> getClipboardFiles() async { + final filePaths = await _methodChannel.invokeMethod?>( + 'getClipboardGif', + ); + return filePaths ?? []; + } } diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart index ea4318246..11d2e07a8 100644 --- a/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart +++ b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart @@ -43,6 +43,11 @@ class MockQuillNativeBridgePlatform Future getClipboardGif() async { return Uint8List.fromList([0, 1, 0]); } + + @override + Future> getClipboardFiles() async { + return ['/path/to/file.html', 'path/to/file.md']; + } } void main() { @@ -106,4 +111,10 @@ void main() { Uint8List.fromList([0, 1, 0]), ); }); + test('getClipboardFiles()', () async { + expect( + await QuillNativeBridgePlatform.instance.getClipboardFiles(), + ['/path/to/file.html', 'path/to/file.md'], + ); + }); } From 333497c06135e1d1817111c7e2f589ee8f59f29a Mon Sep 17 00:00:00 2001 From: Ellet Date: Tue, 1 Oct 2024 15:57:52 +0300 Subject: [PATCH 80/90] chore(ci): format generated messages.g.dart file to bypass CI failure --- .../lib/src/messages.g.dart | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart index f2e6da83b..350dec008 100644 --- a/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart +++ b/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart @@ -15,7 +15,6 @@ PlatformException _createConnectionError(String channelName) { ); } - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); } @@ -24,9 +23,11 @@ class QuillNativeBridgeApi { /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - QuillNativeBridgeApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + QuillNativeBridgeApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -34,8 +35,10 @@ class QuillNativeBridgeApi { final String pigeonVar_messageChannelSuffix; Future getClipboardHtml() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -56,8 +59,10 @@ class QuillNativeBridgeApi { } Future copyHtmlToClipboard(String html) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -78,8 +83,10 @@ class QuillNativeBridgeApi { } Future getClipboardImage() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -100,8 +107,10 @@ class QuillNativeBridgeApi { } Future copyImageToClipboard(Uint8List imageBytes) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -122,8 +131,10 @@ class QuillNativeBridgeApi { } Future getClipboardGif() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -144,8 +155,10 @@ class QuillNativeBridgeApi { } Future> getClipboardFiles() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardFiles$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardFiles$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, From 9ab4fd9e49c0056f28191ec2c93eac070e5010d0 Mon Sep 17 00:00:00 2001 From: Ellet Date: Tue, 1 Oct 2024 06:42:30 -0700 Subject: [PATCH 81/90] feat(linux): add support for getClipboardFiles() --- .../quill_native_bridge/README.md | 2 +- .../quill_native_bridge/example/lib/main.dart | 6 +-- .../lib/quill_native_bridge.dart | 2 +- .../lib/quill_native_bridge_linux.dart | 43 ++++++++++++++++++- .../lib/src/mime_types_constants.dart | 1 + 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/quill_native_bridge/quill_native_bridge/README.md b/quill_native_bridge/quill_native_bridge/README.md index 7b0a7fd7b..d423ae4a0 100644 --- a/quill_native_bridge/quill_native_bridge/README.md +++ b/quill_native_bridge/quill_native_bridge/README.md @@ -14,7 +14,7 @@ An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) | **copyImageToClipboard** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | **getClipboardImage** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | **getClipboardGif** | ✅ | ✅ | âšĒ | âšĒ | âšĒ | âšĒ | -| **getClipboardFiles** | âšĒ | âšĒ | ✅ | ❌ | ❌ | ❌ | +| **getClipboardFiles** | âšĒ | âšĒ | ✅ | ❌ | ✅ | ❌ | ## 🔧 Platform Configuration diff --git a/quill_native_bridge/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/quill_native_bridge/example/lib/main.dart index 879f0c5be..19d938cc6 100644 --- a/quill_native_bridge/quill_native_bridge/example/lib/main.dart +++ b/quill_native_bridge/quill_native_bridge/example/lib/main.dart @@ -251,8 +251,8 @@ class Buttons extends StatelessWidget { if (isFeatureUnsupported) { scaffoldMessenger.showText( isFeatureWebUnsupported - ? 'Retrieving a gif from the clipboard is currently not supported on web.' - : 'Retrieving a gif from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', + ? 'Retrieving files from the clipboard is currently not supported on web.' + : 'Retrieving files from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', ); return; } @@ -262,7 +262,7 @@ class Buttons extends StatelessWidget { return; } scaffoldMessenger.showText( - 'Files from the clipboard: ${files.toString()}', + '${files.length} Files from the clipboard: ${files.toString()}', ); debugPrint('Files from the clipboard: $files'); break; diff --git a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart index ae98685dd..2fc516e32 100644 --- a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart @@ -97,7 +97,7 @@ class QuillNativeBridge { /// Return the file paths from the Clipboard. /// - /// Currently only supports **macOS**. + /// Currently only supports **macOS** and **Linux**. static Future> getClipboardFiles() => _platform.getClipboardFiles(); } diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart b/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart index 0ddc24722..49c26a630 100644 --- a/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart +++ b/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart @@ -37,7 +37,8 @@ class QuillNativeBridgeLinux extends QuillNativeBridgePlatform { QuillNativeBridgeFeature.getClipboardHtml, QuillNativeBridgeFeature.copyHtmlToClipboard, QuillNativeBridgeFeature.copyImageToClipboard, - QuillNativeBridgeFeature.getClipboardImage + QuillNativeBridgeFeature.getClipboardImage, + QuillNativeBridgeFeature.getClipboardFiles, }.contains(feature); // TODO: Improve error handling @@ -204,9 +205,47 @@ class QuillNativeBridgeLinux extends QuillNativeBridgePlatform { false, 'Unknown error while retrieving image from the clipboard. Exit code: ${result.exitCode}. Error output $processErrorOutput', ); + return null; + } finally { + await xclipFile.delete(); + } + } + + @override + Future> getClipboardFiles() async { + final xclipFile = await extractBinaryFromAsset(kXclipAssetFile); + try { + final hasFilesInClipboard = await _hasClipboardItemOfType( + mimeType: kUriListMimeType, + xclipFilePath: xclipFile.path, + ); + if (!hasFilesInClipboard) { + return []; + } + final result = await Process.run( + xclipFile.path, + ['-selection', 'clipboard', '-t', kUriListMimeType, '-o'], + ); + if (result.exitCode == 0) { + final output = result.stdout as String?; + if (output == null) return []; + return output.trim().split('\n').map((fileUriPath) { + // Necessary to remove percent-encoded characters and `file://` + return Uri.parse(fileUriPath).toFilePath().trim(); + }).toList(); + } + final processErrorOutput = result.stderr.toString().trim(); + if (processErrorOutput + .startsWith('Error: target $kUriListMimeType not available')) { + return []; + } + assert( + false, + 'Unknown error while retrieving image from the clipboard. Exit code: ${result.exitCode}. Error output $processErrorOutput', + ); + return []; } finally { await xclipFile.delete(); } - return null; } } diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/src/mime_types_constants.dart b/quill_native_bridge/quill_native_bridge_linux/lib/src/mime_types_constants.dart index 57c1fccb7..2d68f0f35 100644 --- a/quill_native_bridge/quill_native_bridge_linux/lib/src/mime_types_constants.dart +++ b/quill_native_bridge/quill_native_bridge_linux/lib/src/mime_types_constants.dart @@ -3,3 +3,4 @@ const String kHtmlMimeType = 'text/html'; const String kImagePngMimeType = 'image/png'; +const String kUriListMimeType = 'text/uri-list'; From a9a549f02a99b557cea4c46c4e68c0a9dde4cca6 Mon Sep 17 00:00:00 2001 From: Ellet Date: Tue, 1 Oct 2024 17:30:50 +0300 Subject: [PATCH 82/90] docs: update old reference of QuillNativeBridgeFeature, add doc comment for the library --- .../quill_native_bridge/lib/quill_native_bridge.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart index 2fc516e32..850991f2e 100644 --- a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart +++ b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart @@ -1,3 +1,5 @@ +/// An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) +/// package to access platform-specific APIs. library; import 'package:flutter/foundation.dart' @@ -11,7 +13,7 @@ export 'package:quill_native_bridge_platform_interface/src/platform_feature.dart /// An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) /// package to access platform-specific APIs. /// -/// See [QuillNativeBridgePlatformFeature] to check whatever if a feature is supported. +/// See [QuillNativeBridgeFeature] to check whatever if a feature is supported. class QuillNativeBridge { QuillNativeBridge._(); From 9f9faebd6d60c8be8a758aa0312baeff7f5313f0 Mon Sep 17 00:00:00 2001 From: Ellet Date: Tue, 1 Oct 2024 17:36:18 +0300 Subject: [PATCH 83/90] chore(readme): add link for quill_native_bridge, correct minor typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 903ae3120..8a6a9936d 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ Create the file `your_project/android/app/src/main/res/xml/file_paths.xml` with > [!NOTE] > [super_clipboard](https://pub.dev/packages/super_clipboard) is no longer required > in recent versions of Flutter Quill in `flutter_quill` or `flutter_quill_extensions`. -> `quill_native_bridge` is a plugin provide clipboard operations functionality for `flutter_quill`. +> [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge) is a plugin that provide clipboard operations functionality for `flutter_quill`. ## 🚀 Usage From cbe6492bca9a96b74684b5bc07b9b9625817b2b6 Mon Sep 17 00:00:00 2001 From: Ellet Date: Tue, 1 Oct 2024 18:44:35 +0300 Subject: [PATCH 84/90] chore(docs): minor changes to update doc comment in QuillController --- lib/src/controller/quill_controller.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/controller/quill_controller.dart b/lib/src/controller/quill_controller.dart index 97a6a5391..2ffdcb049 100644 --- a/lib/src/controller/quill_controller.dart +++ b/lib/src/controller/quill_controller.dart @@ -551,7 +551,7 @@ class QuillController extends ChangeNotifier { } /// Returns whether paste operation was handled here. - /// updateEditor is called if paste operation was successful. + /// [updateEditor] is called if paste operation was successful. Future clipboardPaste({void Function()? updateEditor}) async { if (readOnly || !selection.isValid) return true; @@ -612,7 +612,7 @@ class QuillController extends ChangeNotifier { return false; } - /// Return true if can paste internal image + /// Return `true` if can paste internal image Future _pasteInternalImage() async { final copiedImageUrl = _copiedImageUrl; if (copiedImageUrl != null) { From ae908caa0386cdd6d7bd5b917bee84f630251ee8 Mon Sep 17 00:00:00 2001 From: Ellet Date: Mon, 14 Oct 2024 05:55:35 +0300 Subject: [PATCH 85/90] chore: move quill_native_bridge to https://github.com/FlutterQuill/quill-native-bridge --- .github/workflows/main.yml | 27 - .../android/app/src/main/AndroidManifest.xml | 2 +- example/pubspec.yaml | 13 - pubspec.yaml | 2 +- pubspec_overrides.yaml | 6 - .../quill_native_bridge/.gitignore | 29 - .../quill_native_bridge/.metadata | 39 - .../quill_native_bridge/CHANGELOG.md | 26 - .../quill_native_bridge/LICENSE | 21 - .../quill_native_bridge/README.md | 62 -- .../quill_native_bridge/analysis_options.yaml | 32 - .../quill_native_bridge/example/.gitignore | 43 - .../quill_native_bridge/example/.metadata | 30 - .../quill_native_bridge/example/README.md | 3 - .../example/analysis_options.yaml | 31 - .../example/android/.gitignore | 13 - .../example/android/app/build.gradle | 39 - .../android/app/src/debug/AndroidManifest.xml | 7 - .../android/app/src/main/AndroidManifest.xml | 55 -- .../MainActivity.kt | 5 - .../res/drawable-v21/launch_background.xml | 12 - .../main/res/drawable/launch_background.xml | 12 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../app/src/main/res/values-night/styles.xml | 18 - .../app/src/main/res/values/styles.xml | 18 - .../app/src/main/res/xml/file_paths.xml | 3 - .../app/src/profile/AndroidManifest.xml | 7 - .../example/android/build.gradle | 18 - .../example/android/gradle.properties | 3 - .../gradle/wrapper/gradle-wrapper.properties | 5 - .../example/android/settings.gradle | 25 - .../example/assets/flutter-quill.png | Bin 24711 -> 0 bytes .../assets/quilljs-rich-text-editor.png | Bin 55581 -> 0 bytes .../integration_test/integration_test.dart | 152 ---- .../example/ios/.gitignore | 34 - .../ios/Flutter/AppFrameworkInfo.plist | 26 - .../example/ios/Flutter/Debug.xcconfig | 2 - .../example/ios/Flutter/Release.xcconfig | 2 - .../quill_native_bridge/example/ios/Podfile | 44 - .../example/ios/Podfile.lock | 28 - .../ios/Runner.xcodeproj/project.pbxproj | 728 ---------------- .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 98 --- .../contents.xcworkspacedata | 10 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../example/ios/Runner/AppDelegate.swift | 13 - .../AppIcon.appiconset/Contents.json | 122 --- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../LaunchImage.imageset/Contents.json | 23 - .../LaunchImage.imageset/LaunchImage.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/README.md | 5 - .../Runner/Base.lproj/LaunchScreen.storyboard | 37 - .../ios/Runner/Base.lproj/Main.storyboard | 26 - .../example/ios/Runner/Info.plist | 49 -- .../ios/Runner/Runner-Bridging-Header.h | 1 - .../example/lib/assets.dart | 8 - .../quill_native_bridge/example/lib/main.dart | 282 ------ .../example/linux/.gitignore | 1 - .../example/linux/CMakeLists.txt | 145 ---- .../example/linux/flutter/CMakeLists.txt | 88 -- .../flutter/generated_plugin_registrant.cc | 11 - .../flutter/generated_plugin_registrant.h | 15 - .../linux/flutter/generated_plugins.cmake | 23 - .../quill_native_bridge/example/linux/main.cc | 6 - .../example/linux/my_application.cc | 124 --- .../example/linux/my_application.h | 18 - .../example/macos/.gitignore | 7 - .../macos/Flutter/Flutter-Debug.xcconfig | 2 - .../macos/Flutter/Flutter-Release.xcconfig | 2 - .../Flutter/GeneratedPluginRegistrant.swift | 12 - .../quill_native_bridge/example/macos/Podfile | 43 - .../example/macos/Podfile.lock | 22 - .../macos/Runner.xcodeproj/project.pbxproj | 801 ------------------ .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 98 --- .../contents.xcworkspacedata | 10 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../example/macos/Runner/AppDelegate.swift | 9 - .../AppIcon.appiconset/Contents.json | 68 -- .../AppIcon.appiconset/app_icon_1024.png | Bin 102994 -> 0 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 5680 -> 0 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 520 -> 0 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 14142 -> 0 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 1066 -> 0 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 36406 -> 0 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 2218 -> 0 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 343 -------- .../macos/Runner/Configs/AppInfo.xcconfig | 14 - .../macos/Runner/Configs/Debug.xcconfig | 2 - .../macos/Runner/Configs/Release.xcconfig | 2 - .../macos/Runner/Configs/Warnings.xcconfig | 13 - .../macos/Runner/DebugProfile.entitlements | 12 - .../example/macos/Runner/Info.plist | 32 - .../macos/Runner/MainFlutterWindow.swift | 15 - .../example/macos/Runner/Release.entitlements | 8 - .../quill_native_bridge/example/pubspec.yaml | 45 - .../example/web/favicon.png | Bin 917 -> 0 bytes .../example/web/icons/Icon-192.png | Bin 5292 -> 0 bytes .../example/web/icons/Icon-512.png | Bin 8252 -> 0 bytes .../example/web/icons/Icon-maskable-192.png | Bin 5594 -> 0 bytes .../example/web/icons/Icon-maskable-512.png | Bin 20998 -> 0 bytes .../example/web/index.html | 38 - .../example/web/manifest.json | 35 - .../example/windows/.gitignore | 17 - .../example/windows/CMakeLists.txt | 108 --- .../example/windows/flutter/CMakeLists.txt | 109 --- .../flutter/generated_plugin_registrant.cc | 11 - .../flutter/generated_plugin_registrant.h | 15 - .../windows/flutter/generated_plugins.cmake | 23 - .../example/windows/runner/CMakeLists.txt | 40 - .../example/windows/runner/Runner.rc | 121 --- .../example/windows/runner/flutter_window.cpp | 71 -- .../example/windows/runner/flutter_window.h | 33 - .../example/windows/runner/main.cpp | 43 - .../example/windows/runner/resource.h | 16 - .../windows/runner/resources/app_icon.ico | Bin 33772 -> 0 bytes .../windows/runner/runner.exe.manifest | 14 - .../example/windows/runner/utils.cpp | 65 -- .../example/windows/runner/utils.h | 19 - .../example/windows/runner/win32_window.cpp | 288 ------- .../example/windows/runner/win32_window.h | 102 --- .../lib/quill_native_bridge.dart | 105 --- .../quill_native_bridge/pubspec.yaml | 51 -- .../pubspec_overrides.yaml | 16 - .../quill_native_bridge_android/CHANGELOG.md | 11 - .../quill_native_bridge_android/LICENSE | 21 - .../quill_native_bridge_android/README.md | 13 - .../android/.gitignore | 9 - .../android/build.gradle | 54 -- .../android/settings.gradle | 1 - .../android/src/main/AndroidManifest.xml | 3 - .../QuillNativeBridgeImpl.kt | 28 - .../QuillNativeBridgePlugin.kt | 19 - .../clipboard/ClipboardReadImageHandler.kt | 242 ------ .../clipboard/ClipboardRichTextHandler.kt | 54 -- .../clipboard/ClipboardWriteImageHandler.kt | 106 --- .../generated/GeneratedMessages.kt | 155 ---- .../lib/quill_native_bridge_android.dart | 136 --- .../lib/src/messages.g.dart | 175 ---- .../pigeons/messages.dart | 24 - .../quill_native_bridge_android/pubspec.yaml | 31 - .../pubspec_overrides.yaml | 4 - .../quill_native_bridge_ios/CHANGELOG.md | 11 - .../quill_native_bridge_ios/LICENSE | 21 - .../quill_native_bridge_ios/README.md | 13 - .../quill_native_bridge_ios/ios/.gitignore | 38 - .../ios/Assets/.gitkeep | 0 .../ios/Classes/Messages.g.swift | 186 ---- .../ios/Classes/QuillNativeBridgeImpl.swift | 47 - .../ios/Classes/QuillNativeBridgePlugin.swift | 10 - .../ios/Resources/PrivacyInfo.xcprivacy | 14 - .../ios/quill_native_bridge_ios.podspec | 29 - .../lib/quill_native_bridge_ios.dart | 66 -- .../lib/src/messages.g.dart | 185 ---- .../pigeons/messages.dart | 20 - .../quill_native_bridge_ios/pubspec.yaml | 30 - .../pubspec_overrides.yaml | 4 - .../quill_native_bridge_linux/CHANGELOG.md | 11 - .../quill_native_bridge_linux/LICENSE | 21 - .../quill_native_bridge_linux/README.md | 13 - .../quill_native_bridge_linux/assets/xclip | Bin 30896 -> 0 bytes .../lib/quill_native_bridge_linux.dart | 251 ------ .../lib/src/binary_runner.dart | 66 -- .../lib/src/constants.dart | 5 - .../lib/src/mime_types_constants.dart | 6 - .../lib/src/temp_file_utils.dart | 11 - .../quill_native_bridge_linux/pubspec.yaml | 34 - .../pubspec_overrides.yaml | 4 - .../test/validate_package_name_constant.dart | 21 - .../test/validate_xclip_test.dart | 21 - .../quill_native_bridge_macos/CHANGELOG.md | 11 - .../quill_native_bridge_macos/LICENSE | 21 - .../quill_native_bridge_macos/README.md | 13 - .../lib/quill_native_bridge_macos.dart | 70 -- .../lib/src/messages.g.dart | 185 ---- .../macos/Classes/Messages.g.swift | 186 ---- .../macos/Classes/QuillNativeBridgeImpl.swift | 57 -- .../Classes/QuillNativeBridgePlugin.swift | 10 - .../macos/quill_native_bridge_macos.podspec | 23 - .../pigeons/messages.dart | 21 - .../quill_native_bridge_macos/pubspec.yaml | 30 - .../pubspec_overrides.yaml | 4 - .../CHANGELOG.md | 15 - .../LICENSE | 21 - .../README.md | 17 - ...uill_native_bridge_platform_interface.dart | 89 -- .../lib/src/platform_feature.dart | 82 -- .../quill_native_bridge_method_channel.dart | 118 --- .../pubspec.yaml | 27 - ...ill_native_bridge_method_channel_test.dart | 31 - .../test/quill_native_bridge_test.dart | 120 --- .../quill_native_bridge_web/CHANGELOG.md | 17 - .../quill_native_bridge_web/LICENSE | 21 - .../quill_native_bridge_web/README.md | 13 - .../lib/quill_native_bridge_web.dart | 144 ---- .../lib/src/clipboard_api_support_unsafe.dart | 20 - .../lib/src/mime_types_constants.dart | 2 - .../quill_native_bridge_web/pubspec.yaml | 32 - .../pubspec_overrides.yaml | 4 - .../quill_native_bridge_windows/CHANGELOG.md | 12 - .../quill_native_bridge_windows/LICENSE | 21 - .../quill_native_bridge_windows/README.md | 13 - .../lib/quill_native_bridge_windows.dart | 196 ----- .../lib/src/clipboard_html_format.dart | 31 - .../lib/src/html_cleaner.dart | 76 -- .../lib/src/html_formatter.dart | 116 --- .../quill_native_bridge_windows/pubspec.yaml | 32 - .../pubspec_overrides.yaml | 4 - .../test/html_cleaner_test.dart | 50 -- .../test/html_formatter_test.dart | 23 - 233 files changed, 2 insertions(+), 9882 deletions(-) delete mode 100644 pubspec_overrides.yaml delete mode 100644 quill_native_bridge/quill_native_bridge/.gitignore delete mode 100644 quill_native_bridge/quill_native_bridge/.metadata delete mode 100644 quill_native_bridge/quill_native_bridge/CHANGELOG.md delete mode 100644 quill_native_bridge/quill_native_bridge/LICENSE delete mode 100644 quill_native_bridge/quill_native_bridge/README.md delete mode 100644 quill_native_bridge/quill_native_bridge/analysis_options.yaml delete mode 100644 quill_native_bridge/quill_native_bridge/example/.gitignore delete mode 100644 quill_native_bridge/quill_native_bridge/example/.metadata delete mode 100644 quill_native_bridge/quill_native_bridge/example/README.md delete mode 100644 quill_native_bridge/quill_native_bridge/example/analysis_options.yaml delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/.gitignore delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/build.gradle delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/debug/AndroidManifest.xml delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/drawable-v21/launch_background.xml delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/drawable/launch_background.xml delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/values-night/styles.xml delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/values/styles.xml delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/xml/file_paths.xml delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/app/src/profile/AndroidManifest.xml delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/build.gradle delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/gradle.properties delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 quill_native_bridge/quill_native_bridge/example/android/settings.gradle delete mode 100644 quill_native_bridge/quill_native_bridge/example/assets/flutter-quill.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/assets/quilljs-rich-text-editor.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/integration_test/integration_test.dart delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/.gitignore delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Flutter/AppFrameworkInfo.plist delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Flutter/Debug.xcconfig delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Flutter/Release.xcconfig delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Podfile delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Podfile.lock delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.pbxproj delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/AppDelegate.swift delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Base.lproj/Main.storyboard delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Info.plist delete mode 100644 quill_native_bridge/quill_native_bridge/example/ios/Runner/Runner-Bridging-Header.h delete mode 100644 quill_native_bridge/quill_native_bridge/example/lib/assets.dart delete mode 100644 quill_native_bridge/quill_native_bridge/example/lib/main.dart delete mode 100644 quill_native_bridge/quill_native_bridge/example/linux/.gitignore delete mode 100644 quill_native_bridge/quill_native_bridge/example/linux/CMakeLists.txt delete mode 100644 quill_native_bridge/quill_native_bridge/example/linux/flutter/CMakeLists.txt delete mode 100644 quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.cc delete mode 100644 quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.h delete mode 100644 quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugins.cmake delete mode 100644 quill_native_bridge/quill_native_bridge/example/linux/main.cc delete mode 100644 quill_native_bridge/quill_native_bridge/example/linux/my_application.cc delete mode 100644 quill_native_bridge/quill_native_bridge/example/linux/my_application.h delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/.gitignore delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Flutter/Flutter-Debug.xcconfig delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Flutter/Flutter-Release.xcconfig delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Podfile delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Podfile.lock delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/project.pbxproj delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/AppDelegate.swift delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Base.lproj/MainMenu.xib delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/AppInfo.xcconfig delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Debug.xcconfig delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Release.xcconfig delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Warnings.xcconfig delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/DebugProfile.entitlements delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Info.plist delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/MainFlutterWindow.swift delete mode 100644 quill_native_bridge/quill_native_bridge/example/macos/Runner/Release.entitlements delete mode 100644 quill_native_bridge/quill_native_bridge/example/pubspec.yaml delete mode 100644 quill_native_bridge/quill_native_bridge/example/web/favicon.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/web/icons/Icon-192.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/web/icons/Icon-512.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/web/icons/Icon-maskable-192.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/web/icons/Icon-maskable-512.png delete mode 100644 quill_native_bridge/quill_native_bridge/example/web/index.html delete mode 100644 quill_native_bridge/quill_native_bridge/example/web/manifest.json delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/.gitignore delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/CMakeLists.txt delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/flutter/CMakeLists.txt delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.cc delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.h delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugins.cmake delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/CMakeLists.txt delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/Runner.rc delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.cpp delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.h delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/main.cpp delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/resource.h delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/resources/app_icon.ico delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/runner.exe.manifest delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/utils.cpp delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/utils.h delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.cpp delete mode 100644 quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.h delete mode 100644 quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart delete mode 100644 quill_native_bridge/quill_native_bridge/pubspec.yaml delete mode 100644 quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_android/CHANGELOG.md delete mode 100644 quill_native_bridge/quill_native_bridge_android/LICENSE delete mode 100644 quill_native_bridge/quill_native_bridge_android/README.md delete mode 100644 quill_native_bridge/quill_native_bridge_android/android/.gitignore delete mode 100644 quill_native_bridge/quill_native_bridge_android/android/build.gradle delete mode 100644 quill_native_bridge/quill_native_bridge_android/android/settings.gradle delete mode 100644 quill_native_bridge/quill_native_bridge_android/android/src/main/AndroidManifest.xml delete mode 100644 quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgeImpl.kt delete mode 100644 quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt delete mode 100644 quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt delete mode 100644 quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt delete mode 100644 quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt delete mode 100644 quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt delete mode 100644 quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart delete mode 100644 quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart delete mode 100644 quill_native_bridge/quill_native_bridge_android/pigeons/messages.dart delete mode 100644 quill_native_bridge/quill_native_bridge_android/pubspec.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_android/pubspec_overrides.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_ios/CHANGELOG.md delete mode 100644 quill_native_bridge/quill_native_bridge_ios/LICENSE delete mode 100644 quill_native_bridge/quill_native_bridge_ios/README.md delete mode 100644 quill_native_bridge/quill_native_bridge_ios/ios/.gitignore delete mode 100644 quill_native_bridge/quill_native_bridge_ios/ios/Assets/.gitkeep delete mode 100644 quill_native_bridge/quill_native_bridge_ios/ios/Classes/Messages.g.swift delete mode 100644 quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgeImpl.swift delete mode 100644 quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgePlugin.swift delete mode 100644 quill_native_bridge/quill_native_bridge_ios/ios/Resources/PrivacyInfo.xcprivacy delete mode 100644 quill_native_bridge/quill_native_bridge_ios/ios/quill_native_bridge_ios.podspec delete mode 100644 quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart delete mode 100644 quill_native_bridge/quill_native_bridge_ios/lib/src/messages.g.dart delete mode 100644 quill_native_bridge/quill_native_bridge_ios/pigeons/messages.dart delete mode 100644 quill_native_bridge/quill_native_bridge_ios/pubspec.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_ios/pubspec_overrides.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_linux/CHANGELOG.md delete mode 100644 quill_native_bridge/quill_native_bridge_linux/LICENSE delete mode 100644 quill_native_bridge/quill_native_bridge_linux/README.md delete mode 100755 quill_native_bridge/quill_native_bridge_linux/assets/xclip delete mode 100644 quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart delete mode 100644 quill_native_bridge/quill_native_bridge_linux/lib/src/binary_runner.dart delete mode 100644 quill_native_bridge/quill_native_bridge_linux/lib/src/constants.dart delete mode 100644 quill_native_bridge/quill_native_bridge_linux/lib/src/mime_types_constants.dart delete mode 100644 quill_native_bridge/quill_native_bridge_linux/lib/src/temp_file_utils.dart delete mode 100644 quill_native_bridge/quill_native_bridge_linux/pubspec.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_linux/pubspec_overrides.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_linux/test/validate_package_name_constant.dart delete mode 100644 quill_native_bridge/quill_native_bridge_linux/test/validate_xclip_test.dart delete mode 100644 quill_native_bridge/quill_native_bridge_macos/CHANGELOG.md delete mode 100644 quill_native_bridge/quill_native_bridge_macos/LICENSE delete mode 100644 quill_native_bridge/quill_native_bridge_macos/README.md delete mode 100644 quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart delete mode 100644 quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart delete mode 100644 quill_native_bridge/quill_native_bridge_macos/macos/Classes/Messages.g.swift delete mode 100644 quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgeImpl.swift delete mode 100644 quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgePlugin.swift delete mode 100644 quill_native_bridge/quill_native_bridge_macos/macos/quill_native_bridge_macos.podspec delete mode 100644 quill_native_bridge/quill_native_bridge_macos/pigeons/messages.dart delete mode 100644 quill_native_bridge/quill_native_bridge_macos/pubspec.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_macos/pubspec_overrides.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/CHANGELOG.md delete mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/LICENSE delete mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/README.md delete mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart delete mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart delete mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart delete mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/pubspec.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_method_channel_test.dart delete mode 100644 quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart delete mode 100644 quill_native_bridge/quill_native_bridge_web/CHANGELOG.md delete mode 100644 quill_native_bridge/quill_native_bridge_web/LICENSE delete mode 100644 quill_native_bridge/quill_native_bridge_web/README.md delete mode 100644 quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart delete mode 100644 quill_native_bridge/quill_native_bridge_web/lib/src/clipboard_api_support_unsafe.dart delete mode 100644 quill_native_bridge/quill_native_bridge_web/lib/src/mime_types_constants.dart delete mode 100644 quill_native_bridge/quill_native_bridge_web/pubspec.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_web/pubspec_overrides.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_windows/CHANGELOG.md delete mode 100644 quill_native_bridge/quill_native_bridge_windows/LICENSE delete mode 100644 quill_native_bridge/quill_native_bridge_windows/README.md delete mode 100644 quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart delete mode 100644 quill_native_bridge/quill_native_bridge_windows/lib/src/clipboard_html_format.dart delete mode 100644 quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart delete mode 100644 quill_native_bridge/quill_native_bridge_windows/lib/src/html_formatter.dart delete mode 100644 quill_native_bridge/quill_native_bridge_windows/pubspec.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_windows/pubspec_overrides.yaml delete mode 100644 quill_native_bridge/quill_native_bridge_windows/test/html_cleaner_test.dart delete mode 100644 quill_native_bridge/quill_native_bridge_windows/test/html_formatter_test.dart diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 47128192a..9adb75e62 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,33 +33,6 @@ jobs: - name: đŸ“Ļ Install flutter_quill_test dependencies run: flutter pub get -C flutter_quill_test - # TODO: Remove quill_native_bridge packages from here once moved to it's own repo - # https://github.com/singerdmx/flutter-quill/pull/2230 - - - name: đŸ“Ļ Install quill_native_bridge dependencies - run: flutter pub get -C quill_native_bridge/quill_native_bridge - - - name: đŸ“Ļ Install quill_native_bridge_platform_interface dependencies - run: flutter pub get -C quill_native_bridge/quill_native_bridge_platform_interface - - - name: đŸ“Ļ Install quill_native_bridge_android dependencies - run: flutter pub get -C quill_native_bridge/quill_native_bridge_android - - - name: đŸ“Ļ Install quill_native_bridge_ios dependencies - run: flutter pub get -C quill_native_bridge/quill_native_bridge_ios - - - name: đŸ“Ļ Install quill_native_bridge_macos dependencies - run: flutter pub get -C quill_native_bridge/quill_native_bridge_macos - - - name: đŸ“Ļ Install quill_native_bridge_windows dependencies - run: flutter pub get -C quill_native_bridge/quill_native_bridge_windows - - - name: đŸ“Ļ Install quill_native_bridge_web dependencies - run: flutter pub get -C quill_native_bridge/quill_native_bridge_web - - - name: đŸ“Ļ Install quill_native_bridge_linux dependencies - run: flutter pub get -C quill_native_bridge/quill_native_bridge_linux - - name: 🔍 Run Flutter analysis run: flutter analyze diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 8e27c9fc5..b4ca5acfe 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -63,7 +63,7 @@ android:exported="true" android:grantUriPermissions="true" /> - + **NOTE** -> -> **Internal Use Only**: Exclusively for `flutter_quill`. Breaking changes may occur. - -| Feature | iOS | Android | macOS | Windows | Linux | Web | -|--------------------------|------|---------|-------|---------|-------|-------| -| **isIOSSimulator** | ✅ | âšĒ | âšĒ | âšĒ | âšĒ | âšĒ | -| **getClipboardHtml** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| **copyHtmlToClipboard** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| **copyImageToClipboard** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | -| **getClipboardImage** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | -| **getClipboardGif** | ✅ | ✅ | âšĒ | âšĒ | âšĒ | âšĒ | -| **getClipboardFiles** | âšĒ | âšĒ | ✅ | ❌ | ✅ | ❌ | - -## 🔧 Platform Configuration - -To support copying images to the system clipboard on **Android**. A platform configuration setup is required. -If not set up, a warning will appear in the log during debug mode only -if `copyImageToClipboard` was called without configuring the Android project. -An exception with less details will be thrown in production mode. - -> **IMPORTANT** -> -> This configuration is required on **Android** platform for using `copyImageToClipboard`. -> Other features on Android will work without it if this method isn't used. -> For more information, refer to the [Android FileProvider documentation](https://developer.android.com/reference/androidx/core/content/FileProvider). - -**1. Update `AndroidManifest.xml`** - -Open `your_project/android/app/src/main/AndroidManifest.xml` and add the following inside the `` tag: - -```xml - - - ... - - - - ... - - -``` - -**2. Create `file_paths.xml`** - -Create the file `your_project/android/app/src/main/res/xml/file_paths.xml` with the following content: - -```xml - - - -``` \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/analysis_options.yaml b/quill_native_bridge/quill_native_bridge/analysis_options.yaml deleted file mode 100644 index b6065426f..000000000 --- a/quill_native_bridge/quill_native_bridge/analysis_options.yaml +++ /dev/null @@ -1,32 +0,0 @@ -include: package:flutter_lints/flutter.yaml - -linter: - rules: - always_declare_return_types: true - always_put_required_named_parameters_first: true - annotate_overrides: true - avoid_empty_else: true - avoid_escaping_inner_quotes: true - avoid_print: true - avoid_types_on_closure_parameters: true - avoid_void_async: true - cascade_invocations: true - directives_ordering: true - omit_local_variable_types: true - prefer_const_constructors: true - prefer_const_constructors_in_immutables: true - prefer_const_declarations: true - prefer_final_fields: true - prefer_final_in_for_each: true - prefer_final_locals: true - prefer_initializing_formals: true - prefer_int_literals: true - prefer_interpolation_to_compose_strings: true - prefer_relative_imports: true - prefer_single_quotes: true - sort_constructors_first: true - sort_unnamed_constructors_first: true - unnecessary_lambdas: true - unnecessary_parenthesis: true - unnecessary_string_interpolations: true - avoid_web_libraries_in_flutter: true diff --git a/quill_native_bridge/quill_native_bridge/example/.gitignore b/quill_native_bridge/quill_native_bridge/example/.gitignore deleted file mode 100644 index 29a3a5017..000000000 --- a/quill_native_bridge/quill_native_bridge/example/.gitignore +++ /dev/null @@ -1,43 +0,0 @@ -# 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/quill_native_bridge/quill_native_bridge/example/.metadata b/quill_native_bridge/quill_native_bridge/example/.metadata deleted file mode 100644 index 194e02a67..000000000 --- a/quill_native_bridge/quill_native_bridge/example/.metadata +++ /dev/null @@ -1,30 +0,0 @@ -# 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: "2663184aa79047d0a33a14a3b607954f8fdd8730" - channel: "stable" - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - - platform: linux - create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - - # 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/quill_native_bridge/quill_native_bridge/example/README.md b/quill_native_bridge/quill_native_bridge/example/README.md deleted file mode 100644 index d659a559f..000000000 --- a/quill_native_bridge/quill_native_bridge/example/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# đŸĒļ Quill Native Bridge Example - -Demonstrates the usage of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge) plugin. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/example/analysis_options.yaml b/quill_native_bridge/quill_native_bridge/example/analysis_options.yaml deleted file mode 100644 index cb0a7aa9c..000000000 --- a/quill_native_bridge/quill_native_bridge/example/analysis_options.yaml +++ /dev/null @@ -1,31 +0,0 @@ -include: package:flutter_lints/flutter.yaml - -linter: - rules: - always_declare_return_types: true - always_put_required_named_parameters_first: true - annotate_overrides: true - avoid_empty_else: true - avoid_escaping_inner_quotes: true - avoid_print: true - avoid_types_on_closure_parameters: true - avoid_void_async: true - cascade_invocations: true - directives_ordering: true - omit_local_variable_types: true - prefer_const_constructors: true - prefer_const_constructors_in_immutables: true - prefer_const_declarations: true - prefer_final_fields: true - prefer_final_in_for_each: true - prefer_final_locals: true - prefer_initializing_formals: true - prefer_int_literals: true - prefer_interpolation_to_compose_strings: true - prefer_relative_imports: true - prefer_single_quotes: true - sort_constructors_first: true - sort_unnamed_constructors_first: true - unnecessary_lambdas: true - unnecessary_parenthesis: true - unnecessary_string_interpolations: true diff --git a/quill_native_bridge/quill_native_bridge/example/android/.gitignore b/quill_native_bridge/quill_native_bridge/example/android/.gitignore deleted file mode 100644 index 55afd919c..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -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/quill_native_bridge/quill_native_bridge/example/android/app/build.gradle b/quill_native_bridge/quill_native_bridge/example/android/app/build.gradle deleted file mode 100644 index f69a5f2e2..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/app/build.gradle +++ /dev/null @@ -1,39 +0,0 @@ -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 = "dev.flutterquill.quill_native_bridge_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 { - applicationId = "dev.flutterquill.quill_native_bridge_example" - minSdk = flutter.minSdkVersion - targetSdk = flutter.targetSdkVersion - versionCode = flutter.versionCode - versionName = flutter.versionName - } - - buildTypes { - release { - signingConfig = signingConfigs.debug - } - } -} - -flutter { - source = "../.." -} diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/debug/AndroidManifest.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 399f6981d..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 551061239..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt deleted file mode 100644 index a36c8719f..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/kotlin/dev/flutterquill/quill_native_bridge_example/MainActivity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package dev.flutterquill.quill_native_bridge_example - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/drawable-v21/launch_background.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/drawable-v21/launch_background.xml deleted file mode 100644 index f74085f3f..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/drawable-v21/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/drawable/launch_background.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f88..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/values-night/styles.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 06952be74..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/values/styles.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/values/styles.xml deleted file mode 100644 index cb1ef8805..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/xml/file_paths.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/xml/file_paths.xml deleted file mode 100644 index 7646ad0ed..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/app/src/main/res/xml/file_paths.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/quill_native_bridge/quill_native_bridge/example/android/app/src/profile/AndroidManifest.xml b/quill_native_bridge/quill_native_bridge/example/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 399f6981d..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/android/build.gradle b/quill_native_bridge/quill_native_bridge/example/android/build.gradle deleted file mode 100644 index d2ffbffa4..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -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/quill_native_bridge/quill_native_bridge/example/android/gradle.properties b/quill_native_bridge/quill_native_bridge/example/android/gradle.properties deleted file mode 100644 index 259717082..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true -android.enableJetifier=true diff --git a/quill_native_bridge/quill_native_bridge/example/android/gradle/wrapper/gradle-wrapper.properties b/quill_native_bridge/quill_native_bridge/example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 7bb2df6ba..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/quill_native_bridge/quill_native_bridge/example/android/settings.gradle b/quill_native_bridge/quill_native_bridge/example/android/settings.gradle deleted file mode 100644 index b9e43bd37..000000000 --- a/quill_native_bridge/quill_native_bridge/example/android/settings.gradle +++ /dev/null @@ -1,25 +0,0 @@ -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 "8.1.0" apply false - id "org.jetbrains.kotlin.android" version "1.8.22" apply false -} - -include ":app" diff --git a/quill_native_bridge/quill_native_bridge/example/assets/flutter-quill.png b/quill_native_bridge/quill_native_bridge/example/assets/flutter-quill.png deleted file mode 100644 index 6c958e54a19aca33de25920f5ec01ea7769d31ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24711 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 diff --git a/quill_native_bridge/quill_native_bridge/example/assets/quilljs-rich-text-editor.png b/quill_native_bridge/quill_native_bridge/example/assets/quilljs-rich-text-editor.png deleted file mode 100644 index e0a434ff421baf6c5d636a708b7972c68be2a4ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55581 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

3V|!*$A? z=wWKWP-UU5mj?30sonN+kU|9^93g@$N=WR)xaa3XL6t8Fv?QEPVMjvB?Ch*5bH&9o zp!z|W5G16gDA1O#wele`M1<1-@{t|v=cmhejG>U`b^H?#;S_861ZoG)XH|GeC+qSZ zStYP(u!H&B@~tfgv*9vz$VSG{76wuZ;Wh@os^=Sig95f2Wuc2SCbNm%iJ3^f5jDfb z+hP*_Ae@OYh;~t#Y7DY|Fbzk}kmLh2Q+#0H6@=JGvXcs7S=^lX77>vSQ5T7Ph}j_{ z=Dw2l^5bA;mO&yAf^ll=>P%)@Basvi)+rFRo7X1T1vw=SjEtU+P7JPXVhaS#Va0O? zdc2q_o;MrmNIir!5ujUESn2NXQWJ1{vk$)aEyX(?Q+u6 zT}Xo$HdF&qmz<_{j!BdW=f!_UD;4-OTF_-37^xKK5Fnf_=qn~S%AkRZ^vA$)m=Mmf zuu4WM>j2VHkl@~m2%~_$30HV1BXS>uI2`4C68j1OJj8J%#u1 zZ_Rcto$XcQNhTXc#l$bIQ37eub|!Z{D4PZRggB&_4|xLYF_Ms8)_qk5%W9iJWa^Mk z;=v?lbq{w!K3`>BTHS(g(u3&0ijAxo_{jPRNe%y~A0z^5bfOHZ21l^1=m_O2l zXxk?aoSf8i*&6dgg2L}FWIup;9T>c`Q{+_^Ej18Vr0O_~;X{Jb1|+unpiUF^1&MGZEL#0Z z2N1WH|Em4;a!-8%N#Kzdpy>-4JO8PRy~R1L(L$_}MAqrsKt7silsgpwsK)^#ItSbq zZr+Yqa4Tl1c!tUh_S>K};x$it5db6|Pj{G~f}UPH;?rPk_UqqpfzMBobrzGHKi?3e_ zp#=V+3&N^Wp{aW1G-H#=S1N&dCE6(|AcGLKT!;u&Ikbv+7GXx;zI_w)v;)$qQ`;Vs z0LiA55Ce4LY|n*KXevQm(byUDqPBre)4EuqJ`+IzeN^b+yO1;VLq!tAtf_)@!l+!< z<&&P5z+U$;W|hSOfp4_2FhI^{mktt1#3@9@#8RO{z@xz&gee|_l|mZZlASMo1*R28 zpfXV51afOMRueKKT!E-0VJdA&Xq8kYNn-vLcO)k4L&z}~LGdjW)qKnZs(~1(VDM$( zp^z{QEu`U@vA2je1>vC}tCdDRJ_LI}dPs0SIPv&>8*Bz@_|G*DtuhCBP@VKZ+_*wY zI&D8nCkb-LcF+T@w~G*O=XbsUN>R+0Dg!*PRNyz-6|7JV0!%Liej8Y~tlbL|P%G|0 ze@iLng=niP@xAwLWr#r3W=;W`A(F6(1%!+(#@9^DU%I42 z49J8~xMiaW1V=d&;gf(3 zM1)rWxf-#4?T`RIO_$dWy2~}`-+a_kzHOxML5{M6+z!8wO{~JjM zauY^+od`LhINrlY2m^H25&%~UY{fhxD+D}=w3vDpkz@&bh;3r6>@Ipep#-G9?QDDX^B`$S zM52d;N&xk{S_%HT2-ZSaHI;O=SjZ;H0AACBui+s_qhWT(t(HIoq7DhI6XNhqdYAr` zQe(N3s?Y&fu5fltNJyB2SPQ}%uqA{oim9SkO7?@gH4yv zeto>?1kju0(xo^6;hOHCD4<0zp8$ipCQ0soa{*sN zv>)fA4Ok#WN4xH%z=;5;w`h@Cn~O^Y7I)gQ*%RWYEvzw=FT5@Y&16O7=6MVcU*U(C zk4>Mp{Tx)@ih#5FQ0$>k077>j=Go7z-jCcpQ76P6@PzJ-gx*ETyBA-uGT6l$E}Ep2 zZzv3vq-Mly>~9z(4W^E3q^9N!h4Q3jzr)^vCvJBNHp63}q63P=ear}euC^`ZK^l&=&!;zp}afEVei@&0M@9WtN84~_X12GULc$VeI3 z!UU6)3&Lksk4v`8oVoeykcO3&6-)_^Y%@SCq~+l$uEM4&F~G5&pU^&jEB+P*pcbUJ z((r|>@(uE+sHnDx*4@ozYp4hehFf*tiv5TM`*Cn|%mLXXzzCIDv#qVI3l}ateEj%v z>9W=9a?=OtIsQ}0}94fjGIc5SB!*$Q@U2(t+nI{C$$sB+7XR=`(+vf|- zJV+icCHBkaw#~T^#Q_%AfFw>rTKdhrD?oyv`}qe%(3O3RGRJ)U^yxX|TF*vn=&ivP zSc5HtN=|(gx9%hWrP3tinLW)@7!GnDwv=j!M+8GV=SP#GUE>fp(p@+HfnVSa&#l^)JNlC>J z)5&>(4 zd+2oicv`ovn$%%9+nYfFNc$xifL4Wb@lz~%aImw`O!GP=B~sBS8ep1jL5&D8Pzns5 z9%N|p{s$qd!0rL*+XfbL?)tS(EY5VeEEI&r@Ek^~`%tT@mD({^S66?z&T}b4CCz!W zkumF@&FoXCt6Bk3dgx@KN_?%R8jr0&op$Sb&zd|O%oU!2Zu*Pm;c{Tu#zHr={q`&^ zEn{F&_4M?zA&tO366Bke>$HIuzu0x37l8I*lg%6nvMQ?oJRrZS>LDqUntOCKHOOjT z0BG?f7pK2@^DP)wZeCtc+tP4(_>99V78aI#(*ggYq6dPz3wit}NazASfsN#X+x(<# zcRMB|B!oDUmezHUrZ6eRT#kXN3b8IIyxC29)06DjeZGDL+K>lS?Cq6qhsR~YR!8bv zT72LycO1V5l~vMLWy5>dwl*sdL7(x)0kqLfCAWr)t-SyWpI8B%!`Q^c%Z+lWggE=< zVPr0V$yTA7)hu7KuDinry~Nm(*Fti|j;~(6toilna`d2aryg4lFnOdbTFLo$nNmHb z#i8<@8!qPq=CC<}XDy}A(aDJyAhbU8$hHd$k(K!sEq-J-Gk_`rSSz+XAl&C!j0gf# z)B^CU9!eG|6_w!HKkDl0dr($B3Fq=&dbxD{dNcgz0Z1xhd;1JI;9SXf(5)a( z*ag6r==y_auQNNly0!sc&5l$GL6tTEfFcn}=UT_RZajn(TzY$3tB=LSyV6uygIRUW zFQ(ro_t}HaI2e^baCksFnn4ipDlae3{ZjpG6iT}ZfCA^QKk$Rrs|F@IFOMCHhT6Wq z5D>drfn$5W@_6?S$VhTvBc1`v% ze@x97-(L{^DCFDXE$fbiY1Pr0nK}qE1Euz?z&IS%sp`7a!oLJ(RgmK00WZ>gN`d&y znNi?K6M)AB(|QUb0oaXSp!95LY>b105u_P1Ll{gbPTQ?yPz69k-qo5Va0_+5# zmVmfbGkqzs9|VEB9Zr@YV!+-kT{$E__Z)8l6NLmefyFjKb=C^92q@@cfp(a+;mun^ zR8-J{76mAF!1iuih?U&u0~r3;)Ko2~SGM5qMATLu;8>@2*vNezi;B`~Q}Ed@HG_4& zwzg(Rw?ceWuXGgv%=H5pxb11pXU}c`!9d5v^aZRw50rd)i;j+t_d!&HaQ1@Rlb1m; zCj$IIPC-EeblIPTaWpWEZgN0Wdx?bQW-`Qa0)yRG4C!9T$;rX7h@YU$Q3ru5M8`Nl z9{PX%1CEzC%tYFs6ZkzCa4vw_+}yl5k}o7C<_4srMxg%)AHqFeTUp%_5Qqd9{}vhP zG+?Dd6jfDK zbsVQi!Z?hCp$q_3q=)*$IKZ}eF!N9Fw>$9C-lB$Xi~yEaLzz=Tp~wzZ_15Qk@_vQGj(1UXPyyUw#Re@r9hTalsyrwVVL4aVtR70g!ff-pU02 zEV;)M>j1u>oy;;?49AJm6U(8%Sr{zv1~i)oT@2bRQ&UrrZ(llb>O4ZV0ZEwHUt&T^ zCuGA*3Bp?>gf9%_c>(_X1|Pp(9OPVBSV-0=U;wg|d4uB|vgiXT{{8_$Tw{FiRMPK80 zxVO1H(q{bzei}elj{vdC_LHnWkaQaZ?wi2tUe$N zuR-G{gn#qu|6=bgqpIrHw^0*CMHH0~1%nU-kx&}M00993MH&GqDGBLNP*K4mrF{rd z>245_kPhjT?oO$5&1e7jIqx`QoR4R`U*0jcdu-ggTx+iRyYK71;+`i@%7FHK`TBK* zRUkwaZl{$;W<3SfAW6&MJD5|aypPIrD}?U@2-VcuBNP-Xuu%B*BR@n2Tt}3v&YZ{R zdrT=~04tI0GVw29t}GJm0BhC?nm5^e`By(e=R~VyJbn6~n;q~0KY)M-!~aV#byNll z`v+7Sw|c&uTJ9|j?h_h9pC48eH_+1qcrJw#C4BgD7@IWtNrCIi%G97UR;Sif5?Ttc z=8x5S2WdyO7_{))o}V1uPt~8}%J`iBt$7W@T~<#2>PpSDQ$v&_k6GtOxWYa) zj`~hsP)&OY89}v+@`=0qPQZ*ofD1^%(ky~>a6;ce22mDTUz6@GfVZQtB>7&@2!cw< z4Y`%zZgJzV`{&zC9`A9QCqsJgXLmQ` z&5g-HEPivo$5G|{p5f|X3AER*_Y%9q!@~L$-p>j1wzlLuE;|KqQy+*^PIg1<#)gTo zHT%DU1BG_8AQX$y_w83^PNTO|_LP;DT7o0Mj0WoIc13&{l2rdF#ggvZx8oBu1}Gc9 zO#=H0G~=6M!7+TiYSypjB`Nf?Ju%Ykyg9$Q3jL|JsL8*N429J?Z<%iizLvez zWvEdym_04McXpYHz3b=xn3i#3`Ujxtha>j~GE5Wyv;jdus367}+?ThUoYwc>n)IZi zqLRQ2!_dq0`b|s>Gx(M>Sne>k4AP^uy}j?xpCa(d@fV2s5)zcVhsPd3D~!jMxZ?P( z^pYnKZhqj5sp%<{iV)W2WkG`R=&xA9m z4e|4Yu*1=OP4nbi7{3uHVq##}^#lSn1{QkIn0ocz?Ck7s;6xyxNb0t3fItGPL1;Y# zx{K|~mwLm!q@;g9&;CIPIU!(uu2%OjhZ0^N10!)OaWGtDy)M6N*RI~)Ua$ernx<<* z1to6ZK7_z?XP$lI7#CQ?Pr&%V86ve&MCaR5jJnLCnFURK4-xaCR|3xme-%0mxD3;z!%X@`?(g4B}+pB;~>M%Zb>U z$kw`5>J@t@Cm{@D+Q=pI2@0MMkBN#pL``j&&KL~r4@Ez-{l|rX!a`B-97^tP0J>41 zKQ{ngadC6U+i>FRVik|9XM5S4^Vm2fG_(!$w!+ZITTCFtUj+nEA^I)vFC#5YAkhOf zT)X6I^LoAu^79jOLczw8T5ORh-u*sDN5>#u^BU}AW3L#gs=9@(d_{04e1QR@Dd2QA zdk~@%^9GD600}GMr@Ev|_G8amX_5P$(YDl>CXdOouXk8Mw-d!(QdYLKq=d2d0-?cG zg$V~kFp!6}_fxNiXPAFCbEf0grZdLBa-6H}GfPVZu>Su2yHn=l>26_y|4XxjR<#ef ztgNM_Wo!OUAtueQi|=1da$Gi3O4O9WfQk2(W?1irlFk9@BXS77hY#XC%<@BU=A zTATc4?L29^@+F^qGB)=g7q*0;4w+K}j1o*Ho~P%`NK{CU_~k#oH9aDIlV5`FU2)K@ zr1>A$n5MI;H-4L>ykYvb)}H#sgZ@}PUHaShHDij78m5LAO-YdzrWb6xFU>L zO4H|lhy^dNk@%2a4s-fmrD1!smg_^1(Srx63VAIR`$H!q7rmHPrku_w7Uo7qm8j+@ za(f+6vGrNjn*S_LGe0FHXQNRrvpjxRV`bQ1?Ur3v@KZ9Yxy|;j10tQ2#eZU1MojLG znEb99n~;tP8nQng9#V~eq}7RayF`4DN#v_#ReR2?4<0Ii!wKUx<0*oGADo9J1I)`6 zjAY5Fsr{Hb1~CtTN@J-+xWNM;7-p2asiq`*_8_x+7V`dg1k|2{Bxm9@u)EO9%E}Tb z8Qrn3xA*>1D5y9HZT6o}UWsjL$gXSq>I$EknMX*`yadNV6edhx`emSJ`D`Yr zq3CdRWQs>>Lz}ww@#0hP;y4gDA3R_H$!CDVqrV2mtLOojJjfxZ#=u|>K!%IP^~+J6 z_lJRNvs|}C+S=Mc%09KP|U@>sKp*VN}BU#$W%8$02)_g5X1XA z0$3^SCG0Jb7nCzhXfUZTb8|P2*?7h}Wh&X^iQykRhl69#@nbo7S#AG>)7M_qf$vUJH4Pg+4ZMXK)(~-HKXLf* zKLOW>V*@4k5G`#bGQQd9he!-8Otksn1=ZAZbQxtjCPSLQugl+I!RAS(c z|D1ekIVo>W4S)%<4=zm+sQ&PvciGU$ihqAq3>0|$O+_UjFz^>>gpr15&xT7PEud|P z_J?`uO0>eUv5*G_2J;|OU{5#>l6kJWa2=eZJKQbGzV%St85kI58l*XJos@CmKy6(% zuBW7-d6k-)+GdW{6aobZbNGr*z9#+h%5+<8?K@=kANBntB^XkrW`Z8ZAGuq)(V48f z=kn#t;0FjM0iPjwSh^!CijBm@-iKw?G#RSYUkZ1AvnHl=0o~cLt1;nU+pKO-iHsS zI=8Rc{%Bu0w;0kXQ2vBu%0y`O$$1MJ6MA_%a^^~jE0LtcE3L$5mR}CK7}&ef+=Arm~ivW!=kKUq?HF%cKAX=CS%rM>m7<}rs7`IuBb330G) zC$y2{0gszHCy*)Ytwu+g)_L9}kPV%ZH0Ko+D{q#M*7FIAZ1$UAL@g}rXLZ>yBBX#J z#e%H6&{khaMfahSv#Dpj$0&XqoKEaG5{eXHjlr%4@>!fAaJx1-16Y!lvEqjy0RC;q zPFbD#j=boWgf-x6ofZ@g%o&`>&_d{aDiwVdGl1Q!4meb4jN0;F@8C@WkiSrk*N23` z$H#Y{E6~@Y2|D3Whr_UiR;cX#rVqJsBZG-|tSJ^q&;$O|`5hgynZ3(uzO5(RwS0P+ ztCcHGi1CCa>w$ZH4fc-Lj!tLHL})y|cPhp*d*Qwivy0r|nl0>-Wg2 z?tPfvgY{-=rIXn)UZ-`qit_T4)n7_-OH2QDza?(^dZ?+QCa=pSW3I@R=@dmvCyef6^2ZCSZJyZ4biJ^D0^8EPomY!B71=r|u37Poi^CesPe>_xo zkE)1MPQ5euk(tRpw`l=}S!t&N6^G`};^Lk3^z^?UUfRBWJAtF}5ZbHY8mun;=Kvj&k_}hH4M4ZY1)F&o|$I;;Ez*69Wr=GQo7| zzg_^$jrLG?Z-2e>?AfyFjd?HM?(4t{zdJZy6&7`rc%maxkPpDPgU?9)Aka+sE&t?B^G zVchT%KMTi&KDbo z+6N5+4R>~C=61#^QpvDo(iw+ z4Xzx`sJdngW)t*bPH8oI$#OwB}&M_4@P zLm4^&z4_|DrJwqZI8L272Iw|eKHJRgFyY+_6-fWS+yerfpmxzts~Vh9%;9Hs3l64L zjiVevawY?{N4q50m(M7%K(Vv57Xf-nnH)= zAsm5AYR(Ol9x8cTG6!P+?z&ynD4Wf3P}9-f`|ah0#OWHZxBQEB?)SWDl25H^MM)VJ)u#1>dEAxXcb7r`X~%LlNIjMi*MxL7w_CmD!(z#J42zx~wyI<^8_=_{=(k zTZK@@L{_3XXKpG7_g49GxOo(piStq89#-i~T9w90 zlAn$Q#N165_^hTq=0TkMf-zfh9`+J9GrgpyFLee%Ej}bXj|{Htw2jrxDPhz*Hg;n0 zZVYWcOHf;=bDPirTnB3dCsKgeB^4ef?~fmE0{8j7cE#)kuB6sBBM}@4z#S3x zLs3ROL6`Q-%@xcv@429$x?|_gQKS;R1EBB_V2;RDvN<0-c#vR*ppdX|vuNg6ze30n zM=xp@*&WtnFylsGRCI#Ozgy}Wilm6JWkBs3R%@mz{}3z2=U&+`Bat|(Sk?TC=i|y3 z4WnqCeYfkS%jPsU*#xQOZt2MG9tNDY(YmFh*}|o7e#_n2`qsnoVmJD}@^Y8oldWC# zFOpiEA2kn}w1~wr#l7J*Fdxl!;h2`1&Q>$ZQ*GOwDcTU3dY3yRno8E@A=g&x=IA-$ zvV`cQ1sT`Ww3UG0DR}{+9komphXXV}2{|g<;5d0U*iV7gfToQ}q?@YjSxdf={4W*z zovL`hUT+H~vOiuQ=sK`Xgx~2M=KPVrmZW<8q47bkX)^;hI!tA*a4Y5D025@3l++GO zR$aJwqhVCX^d3=f2< z&? zj*Ngk%50j%aBv7Uia^a6=;98ZqNAsOry8G8?v5b?1~w^>Fx8vblE6?)Qv~ND9|VBu zy!p2(=L3M^xnM*m6dxs}nrR)>E`(~IK%hTQK8r&Qd8Z!puIKE?K2|Jq029Pz*2$O3 zgF%JRWgyI5v9?Z|vsY50#1gk5#hqr^mB6{b*-ni>y@CqV3SJIfid`WnocTZ6_h?fr z6(EG7sxoX2=xH}VDuAT(Y^x(k4$QDBWLd|qgGnF+0+Y6sD>e3}4N;`vNcx~bDfuI8 zA_#?zS2{o`HX4`xXIYsQ99K@u{nOLfLgegwB?){9(5N1|P9C;PRjjr=Oh@E84EKF1 zq9T(WU*#i7;HFfZ62(UZe>RW!$UNF7>tG40-YS4HdrrU_bQsj@)baF-%NNT93-<@T zd)H{llV;Wx1TC$|L}?f*ULE)nIuWOGbQ<56Ua_>aEEq@AWJ+KB$IU=?{1WcU%wYdM?VzU>A_7<4L*&0+iFVUJ z7W!4bG>rZNBg=TAfMvj64t~y@>=;XPrPxpQj0T@GII01WqYMVClAJFKyy?+t3~Y?{ zx{BevPQYzOaN4A zn>zXaeHZNh;&8PPzxAa+lYU*j;N#t{)oP?!+e7j`NL@D(u%Z_S5aP0cq??kW>sX>U?8@h?hXZk73_Cp_l7X#=UbFl zkB067FE`lN=N1|osuI0qfhkMBM^8@=quYuBVB4CLnY4Yy9K@{yAv|h z-&T`q-uM}PZr2DQYHv$S#| z_lKW*MClK(-D}pU{QiQ@(A4swghT>UV@ir=ll59oTLW8p;LgK01mE42?oe!)mCvD% z(mxU&A``jK|A>JdU)zjI;kJq#W656^Eg3~ZKObXPyF6;r#B*-?XWPhn*`fL6Wr^4( z)}l|HG?MP_?tXf1Zf-YaWEgRco?sW1x1V2hm5qT61Y6A1|Dg7-W{3k$Lb{wKjuMg< z^RU9yVP8jvSyu(7*l>YYuR&3ONYEE6V%|f7x;j?I}c$@3@dVdBYq7t zhsEQ^#KBflBZEUhhvztZX)v37#4fPdgE*`eg;*&P`0Z}&P}@FsAVPo`HB%NftdfI< zhuDEwV#_?}6hfVh-Cq0Y)2B1!FI}*8(GP4-{B@H?(WvimJ^;l9)~{%2q|U!YCYjR zIXQXiCsXYy?d);2%4xTO$V*s1cM983S;p~k_*^%I2p^EH_kY|F8E=X;u*rwB@UWIv&yHn*q|aYg)o%-g zz6F>}0SiE67Ism?USM!3k$$-Up){lUd~?YK0Wz^}frtK_|H!it$D2|w zK4?WOq{R+kc!Ae%U_XoW*XUFUFt1H|5_sLlok`;zY#oiw; z+Yq0m@G{@{!e-I@_3NZL&9(z`4YL>H?{6O}JC^41(OaA0%!Sa)cQx2*qHC0@Bj{Wx z%eLa;nnrc#_2rqr&#g%Fcl2s0R8HIP>Vjm96=yG0LJxfJ>I#jEV@n;5cH5-eugJ9j z^lRB1*ubS?8nw;M<*?!6#-PaAv+f|=Bb8!T%S1Py%Ep}KTiobUgbohd zbS(xiLIp1ePjuT;&9K{Y75#){m!E=^V1rZSu zz6sjR;CKGHvX*DV>wbzuBdoeFbdu)*O_e)^+}rsNmExh?r8k!7E46Ziiz-G7_Z^hiYxElAs&8Oy(0-?iLDjUkYIljE$wyg+B_~UV!JjAIU0x z$VjURiun$Yam=MmOxPf|5HqV6op8HOATmK=03azTDKFo=N&oUwL^7uF&%{KCL1ta2 z(zon>nIYy27Y@cN1rHziiI|)d);}~QJAEtk48q*0Kkcd|jU0m0D@Zb^r zXvD6VVJ@qoD*G{)uyu{0%xUO|jC&W-jBm#)MMCDX3hH?rt?;#KNBi?^kHMHztA=kp zJ@?}R`~i3)#!Ns5sAn@XGlWz|41W+wKD>VqWn&kB#Lj)Lvep6`VR~Zn3Fl!vffv36 zW{lb8HCM~Y&u4!EiiW^&hKtJ^p^Hb@ zra3V=Iogpqt=O2L^p&mjm%>-eF;e?ilI2D$AA__{F?PMo^jwr+qD|k2E;%8pqrTci zerKSt_D97@gQ?2?NC6-ZTi27FRuyaxm@8E1B>fpKYN_@=TrI^J zp~8}KfQk70Jx#o=d{HM*B1hUswrpX2O^t0Y*1oneMAOclxJC25 zUiR7|*OJNcIq#BZWY0g)zK0ca5FiackCHgcIEAltO zWgN)rXtS;yrQAM9HRziwkgm$h%P-P;ZA!k|vm5gEJ5Nu~Hm|7njf}sFi{0TK(YZ(s zedfaN`-9CZYis>|eLql*=1=-mJ>ar{a7k5YnpcP3xbAqTK?KHZt>_*FW6%x z*M24+(#T{(jX)e)I307E64LO}Q@@(!cl+I2mUl0@A}(I4mEx_1s|t5;{rIBNPIICG zNe_$qgod8PnbrZ9yqa;wIc&`quL@|RlqeQie-m~!OWBQ$jaRQ_ zk5T0aJa)2{mRx9zLrwRzZba4-{PFe~Y~ndWPoL<^a(l(f8`PkIbW()+J(&CmA0c*p zA&(>jHIOS=A))h?0pVu?PxJEa+nUVuIj!fa@t<`{UK1q<5s_d-Za`d=VrYdZUTf7B z6B$YZ3}s8FuR>PwfL3ON8(+DFW5S4@#C7$YroT@~FCJCAFVbPC1G0D5+Q`d~N+miG z?mwiYcrQNak|DV?cf0i2bBQAYLe-69H)<8d#ywbQN}Xy4!;A3U3v53aM{_%)&ZusD z5gd^Z&N++jjjJ{9FTn!FuHdlm|7jfwDyjNHPAl)>!BoksZhDxGY|RV6kpHnNUcnIM zx9^L1B$N{s>-?LyZVdpo)Cv<%>T-gQHHlP9io@%r;1-_deilFgceWXLp$TlGa+^+~j4|+Z{CwL63{m zn!Xqwo49KELd<@?i?1$_{;YTWXF(=TPxr&Wo{4HNajhVh;3%N% z5;rw9HEQ~HjG*v<51}Xa1@07v@eYpSIVg}r9b}hLI^ob$5!xkM)6)}PzPvL!!% zCSgGzjH5U#pWq-SHg71+Pl8oheajvgj^Kjj+<;_kS35}RO30*N;e`8;;fzI_u^n$- zy&{b2QdC7$8U>;kG6nlr=bP1-Plz~u1csvIKcsR8jntQ2%^!VU*$K3mCgaEM-X?vf9T?682|&0zVtt%S$GRP%jI0HV?%UHd3KJDx-eqm^P@%Ht+*Di zBn@*qpY_Ngd{kcAckxNX4Cl{#Vr7C_bs;co|Kw!o^s{r#cfQAiTuEy zDmPSP(~d9RxsY*CC6S-X)TJl0<(p)kLGa@}yH_S5m#ANHGuHQ*)wM%S{w8MeXt!u1r~Z_;^gOe;0y)hZUG_P$;t9_BKg6Cr6|u} zkI$__TwMW!ppx={6rOg_-l(~OJVq2yD>HNCw!Bqe@LVYQl+K$q*Lr;isl(1`vsa(v zJeqY(xYXXfmh8~=-RJeom%IP>OEP&clcBF(_41e!6Yo9N{`uluYO{qrIoB#==w0@s zkLpAmKYUJ?=d>>~)uU%pl)YcqChKngDkSaqlIYScV=zfjz9Z~hO7zBBzP_u=g*uwS3dTW5vPF=6|t`ITu-mhA<*iCrE606JHf7F0}0$P>Yr$KB8O28*KGBUaWOHm-#C(xF_ojV5m0{i;svFB zUW%930YJOxJ99EjM|jOT@549)tgmG>soXi33!4W_x~Xl8cW-CAFPOI`6~*Q8QXe2l zS`?1J{6nkxp%NXO0amz!!RKLv z2^go=jt=5y)Pz{|o!NukSP)i*i|D&HjiwxK(7SAroht8&OfZj0Yu%Wg9~~VX%1D zmPRIN%aZFzX3x+@OP=4;Ib+}C7$+7%ar4)GniY`&rqJkA)^&yMhEX@hXB6&avShkZ zo^Kxhra5)sdzX*_?Vd-;Exi$qkJgv(Fg~#ss0>ZaM@-qq29@7&x1 zrlL{@%2u2jKg61;NVw%a&fBVHPyO3jUw+j8WS(@-q2)7VtFsJ+6sk*FvX_I;MO<7g z)|L?>{2b%O)8~tyMBz?%nYr-y)>){qzjx%vdH8n~itx~0h>D6NoBWnN=eqUk?CLun zhB30L?jzEcrH!sfY%a5k^DYlWPMcYcNTpa#Ed+{-Y|05`((51YHFI`S0ow}J?~7OC zUVh!nX6#dFS+3fJ3Q10I3 zV1IrMCnM$VzaROYH+}wgzD@i{wP?Xrhl~#kY)AePtBITFPxOlYtWdPl-0fs5A}=Xz zmJ_q}VXEwWK)ky~dHLlALzi77k)-z=$r=UyKU>ASJr4XfIQ+e(ShKI#lyhNoT|l5C zWOfSPp9r~p@7DDXU-hI6+XV822(JO)A>X*AzWSs_B18D{{v_&qj2tM{cM>&2VXYX> zzIF5FUTidx3KNU}F}JYr0NfNLZa+}dAH2QYRO3qxc^W{9Z%%J*CVtnra`NO!qu*aX zaGe&@Tr>Xtp;t$m)RZBFr&haDvc)y)o0)8-ly>2ewScd=q2ihw0jSvfU5g zE#Zcp>L~d)XAe`@0{x z4G!Y#{{*rfF40?(*KBMqqsDnrbGpHTcg8l_1ZG+MMjjT3b{04X!+VC|gqq(ns-)GS zM5Ul`uHw8pgdNnFKhx8-xF=Y&3Y?y{EH5tVL2D(vsqh>UA{4>(=gpEJb&)D?-ds$% za_h$ot`cr@C+s??LU083Mz~Byet$iJAmk0%dtzYPxM90TGW;PM|qppz*YI>|hf2Ze-%ziJhVfU2PE z=c7c~Yi*sG%AKAgC_h{tFY!okS0JT(a~#~*028Ok1ztsNQ(^`2*c!sigVPefqkM?GNu%O{>0$FJ!pwI;GdZ^~Ufw>zJR_0~-S^ zcnJ(LOfkJkJN%vBkK} z6b09Z6eB-}H+EF%r@2zb$7Vg6(`V(7$$F_J$^IXSa;@VFE6;zu06SX9D}C!yoUiZJ zpUNqARMUO*``fR}LDa2v?zMkOK9cp%D(xii-WsA5nGTJSGCHY6=1j^#rS^hnskia$ z^4^ez^KVrzi6ms)G8?`o$5Uucs_`9I-_6?1Q2-(ByglO*haO{PN{h{!YC$Stwg>F@K?RovkG<;nWrtcRTJ zp@0?!7gnQDrp;XFq^Ql(aI=Svv#I>Z!F~~#h2v_NG8e80MP9z4rcx=;$UM|9_Vq=m z%h1BBm~qA0(8oI~3q~Fgp^)0U@ic{jZ|Yfpx_X|S-mP+=?98*3X+|;LSuKfZ*6y#R zH;TSCJwzuqOP8({{3k5$i0T5Ru^h>te*++|$Eto-a0rkRc@H96vYWZf{`vppJzlzf zK=z~W`G@4r|As}@HGX|k{%z3w8oHf`D6J-RV0porNkwH= z!>}z4tZYbu8$tw|7r@+w7yPiAi>Q1yjOq0$9OeXQ!+I=vla8kGMKABN z(;#n8Si*!I7Jw~D@HAN~kd?@)Rl`EYk8r^@xPu6%0KUoyCDGTH%-d3)PfVD=7K5iz zJwTB~c3?l;B>>?n@Kho*!i-T3BFRPQ%IoXvzKe?v!nm^fWp7Usk9=wzUBMQBFL)Y} zcc~kjPw*7};ykElLc;*V_!{bziU|DtoVuk4h@aoRlY8127(O{nVWd@j42T8(Vr78P zKP`$zs`c3;%xQb+YTXGP8(tMUA-iqg@j)RtVG$n--0I44YYzf$;{6dGRU&bSJ_b$c zAFV={g{dxDkktmq_xXeq79a4wNJj5uW@ZM%ZP)*XcLWaYHhb*+AV^=r^NgN9heSSb z6|5GKsC=k;T-fh{D@o*U!1PABZfye1dFHW?{eOr6G4&4`o&=mf}AF*l7~X0&!=8HP%}!==&D z(WvNXES1=g7H;;zW4EveUKL{>xM53}rRpB01JKH55B=xBZ6ZdenT7j}3pLCNF|`#T|yQt{6m57__iEgNPI()}Cm>!E1O4DTBPcJY;*m zfCM;1MP)#l<{l!pf^YH%-kE%C6Osai7K(d985^zUk}Ug6$>6Ri0Wv2&aNy_vx0^~5 zU+2EGDjyhg2&XuPO&-I#?-RL5EGPmJ<>lw^52;!2l&KlIf1PV!-^P!AN&~b?k#Y_(bJ|92-2Y85qMBCtG z4(q6>u0^C`4tdu8!NKRh9`xbPqFkkc?H|^|k*-``c-+L|m2{CDD|8jb55y2{=itEF zf!I1Tlo}`O9>l$kPhWmK;myPNV-|Q#hxE`EDzXl zS}CRvvvm?~b6MNiq$}JyGAX0g{iv8M%BW&kWSM_#QNTXT3Yok4K@Qizo{g@a8!RQ# znsXGJV~Lxu@8yr(waPe2OREc9cEWKYIoH&a^M#!2-^RS<{OQ5aX(#4wSg=m-x;gAx z!x*6cqi7HPfjiqXIh_9Nzsa&XcCSz>U$e|nl=jvZJ_(PY$np@ird9Q&PE^N#-x#-2 z!JJ>>BY00YJDW}(a67fv?Nn>akA;T&g$=4!2no)26mE2!fbTMu*J05ibpM5{C)h_5 z2>08Livj`_v^^{0n{ymn-lL;N#ELp%Q%Lr3nBXvp1TWi_y z{;bx&bK!|0D?4d>BGGB^yg{m9X=!QJ=;vU&TE}i~X2JWo^Yb<)&4**PHds}S=Y$LZ z4niLNAWiMKpqAm4nK#Iu)?xZYSatZ7H^ZN_M%n(J&B>l~m6l73#CyP6(;B}11J4dQ zcGfUkHU2$#UV`Evjf{boWBuu!CLydUqMAaZ8G`)eyh5!*KU~|(bG5iC?q>`?eQ!{K z^Ta_ZKcMxpm>kS%+t2yv(I`zxgQK_3~yH5Tr%JC z=gs>=mN&r25h$D>#Ud*>$wU0w91Y(j-O7^4PQXqW-uzLS)WBQd<@!!e+wIxHm|A13 zwbtI&AHYCr5Mn`s4V%QLv}(`H?EgEPkUex<^KU~yjgh%GLkNNmUhm#vRot?Wk*i2( zvds@tW;`N<{Gy16$eL}y?7xZXY3n)%MXM0?u9|O z-TUn-)w;QYM?dP(nA=yXWg#A^-=YDc7BznmgJD?eBqAQ=q|6}~nGY>r-*5@Hs^Rz0 zeJ5;_!473-a3ZyKNUi5@^MnItJzWzMugv^O(^S*LS07Czmsz$O#mp`yx;oo*2@Ag< z3@9Bvo0nY|BwW)fl~+T1Rzvv%1H}&Q!hed^uhy}*Cc-<5ZN);<_O%^)Z8I|$agu_q ze60FjG0**=DtPb7QSkA6LY7WVb&V`4-9ldDhZi0m{8L%|HrXsfV_zbN9_X92COt(c z72f$ijU@{_HMHSjItu9nl~Qewlco0x=8ntRz#d3N?K zA3i(V9Tom`Rnvegb=_~rLml$zz!{I^^|MP-yu~+8=P>e*eP-9_s8@P@z5GGZ^+z~o z8Eq}ZYA6yC626g7?qO-wu!hfF5X)we%$oVJpC4~EJgr)8;ZE(1`-LaxyNI)&+)P~Z zM@#8=B5RoFTB>NU6?-hK;N;}fv&s)I2ghmTF43Hno}ZjASexp<=Q4P2i0nI^RTZbN z-Y-B;&B^7_*5HDWX}*?+m#{*RXYI+J=%YX%0-$dLu{Bn0k(Bs#A5m7*|+y`A1thGr(T3#oYQkudA+++P3=)ozN5sf4)GTLBIE5uap|u%cj4x6ne~*H znyeSV{+IE*l@gAyQ2&_pA*^y4zLaQRJ#m@1V^(`)_ zG_tV_2|eo*xG?YpLDX9-ANe`(B>S0(Ztg@cx?1!^vvl zt}V#sEFDicIeqBwWRPjUS9te zmFi0^LPl>wi5s8r5W&?dLN2m$DznqF^1|ae4bB+q zjqbGhS9u<;f5TZe!{=KbEhXp5n$FRQtZ?XdrWJ5c&xP*>6dxi=A%2yrR${ZasmR=i zcQaJ3vsAecB(64v!^V2HdyP5&*GU>0ZQS~riM-~AKC?-OSUFlp=JAG&Wyv=B;&{7N zUdT1+KJMn&hw~fSL5_PIyH-*x2c_`AZ0wVM9O+O`Yzh4Mu{Co1N0`I6;l53QA-Uh< ziz{n|TaMj}3AU45jI*DE&HB*oNy1mF42Ia&2AD;&D_xd`m4nQ~+xUh7%wUgm?nd8zFq@nZnIO>*(Bqxn7kx>x9Fh2zvr%(N@gX9t2Ggl3f9dYM)~ZyP+5^OK(tL08Qy7OY?3>P6WkA<*KoYK z^LefHJ}T()#UYbZ1#4zZOiVUyJvF03wU`>i(_~NqbkJZ;Kx+kHACvBSvFLj7gwG$e z+93Wm7Qy{xJ?OAUZzM~MPt=TzoFm#0aR5cP=0)vG&TDcqE2qUwhSa+E^Dmo?%|~1# z8Pd40D{pS~+InH8IdhAsBd-#^j{XoMGhhILQHGqZsl1KdcU@6MBjK3V3($l3!%M$0 zBEeE=Y12M`WSm=gGoM^=A}RcMXP27XWA&Et#}K58Pug}SS8`1YZvj;OAXw#v{O}rO z_itNTODO>sco3`wJpEQMW!2KIIOzm$5nqw)67`+MDy-_HZ~@q?nd)#Xl=g33SXc;Y ze;_xk-lZX76PjLksD5PKH(u&y10}|B5Q56 zDcFIL!gf}Yg6P8UJC@iRWSsD)MsT__ znxr-?!%Ae!VVK7_K#%Qst8>cu{)1oYTfzig^TuU5yXU&+{Suv?8z<)5Bxa~VXGTV2 zJfuIDIpQ43QNqVbs%he3XXJ0hb-QyO++G~6)Li1akUNpWuR9EXBc~SNp51DtmaI3! zxoEzv_4(EImV#1Zd}uQvH)ij_LHf7vSNE(}JBKYUSeGuU7=)G(MFoXnl7DieIkTVy z`?qRfpwMp#O{~RD(=kf4Ieq;X1oRHEZjcmRoDX~4tl~87*wg7}%b<ou)N!5)q5~5 zwWcp2cd8HJ;lCzpp;n!6)~w^JxKy~PR+!%LwcJtHc6#f&V`G4$uASY-BeHx?36!R_ zwW!s@9BveY7MB7QmS2=Se_03W-j9&4%vC9ZC9e}#`x+=nP?53U?6?; z29e&xv~+*T$XZT(ZxQ|wkija#MWd(^15*EK01J-|kV)>4Bzh0a# zZ1eh0rmDQ+aH#hr8fR9=f!MO;;pbGE<#?l|)CAn?#;lzRb*t_1y*T9}W|3La&8g|l zx9^-MbW_U~(-WQ9wN`#mh4FZfe{~S(cd)khm2)0bBr1;6dOy9vqet%}&e<)-YE|E$ z(%^~7&mEtS;cBK!)Jz1nD|@fkX0|h2&SXADbi;_iRR10fXQ%obRMmk)_IQl!+#7Qz zcff2v@Bv8>*t%!m+E$UCm>SK-u8NA);qY1rf7810gKqmT^fo2BEV}Ry3ChUH%?H*x z=`Ve)ewT@6ccEZK2RGv#BDfESQcZ8~sk3L#;^v+HgVB5S!kXah&m;DDq-ZVi`3PwH z#NYv&uzbGeu&KEtExHy9QNQ{7y|`EMd)9l4AecC4(?~CW{k|qt<+Q?A2=L zut;V{yNjdoe{bUP-5hq?33MzMpPilQtB|+WH9bUt2Rl@@>tVxu!+d};D~+NXjr{5T zOAFLfc}!S486>*uBl-tV|Y*wuQj}v9>nhdMa~xT1U10$D997I%3-*n#^iz zhFb&m?raPWuW$J1(dCs*uPpa#Y2`%id$Hcr;VM!vx<1(dj^v|lh4f6PHFoNPgy0n# zY*#GeHo%}oJb^E7LXV@PM`kr}e*R)YOiavbxrE3M`t>6EBq*O7T_LCJn(e2v;U^|| zy+ntm*d<%5e<_0%egt~9WdUO@J9LC<9fIIm<8pd!o^rS?<+0OJD(UHOqD@;ue;z+! z-mE@S@P#s0((v%qF6+Fne5wZ@088Q!!^76uZf{~&h2 z{f%oL+UGXxjU6~iW?cfr1iI1X`yYtjt3u&sCN{HwNO3r>S8-S<_bDo-y~~a_;{7Vl zJ-ylgz?g}?Ih%noQpI_n-XWm?r{&z9!2usR{a@aUYq>1{GcKkw2;Drr*UcbAfpu4> z%TwZ!5m81tc(gP1E>US45t=1mMcK9<8Pb10@!qf-|9tN7*?Xz$KR^EGGN>GRm{UF7 zmM}`m^}qi9Ubp-7`^7T<^M3#5zgx>No&MVm|NGlZ$%m}>|1Yn$*Ug!2*Z+B$e=pDf z-fI)F(e(sxlGVZ4ENPQdEe5Hi+0jKbvx(q$r-%U6+6AOQJE*D_9=c7GQmBX>tkV0S^NOQr|=rDR_ui9?uvsz~`p|5SOsbWoS9+kL@}i{squ9mee4 zj(;~!44+_C_-RDucP`4ChvfLp(N{V*PTW}Oye(^6Z!cM>RX4pX+2_mmw!WrM=Z0?m zj+Cd{qr67a1*|h=PUkWfXz5rSlJBc&tfC64kRI?l|q_tksOt9#=bZ?dT5 zJm6PR)gAXI&3Rt(YnLut&GDlzZYn>c;1MVJ%1e85cbAz*%@WP^KUem@Rd+lSt3+Rs zzuZ;wf?o8N|3Pmi_n(sDi9L}#j0$2ws?BklSI@p_OVi%jADmGiHuWcd&u$B*uVhSy z<|9;$%`Cp({t5Z1=Dx@%ew2rb;dd}gyll9cV9a5IDApaj>|SvmG`{khnw%l|jDq($ z3hGG<@jr1tIt~b3mzPyqR=&q|Dg9*L)%O&!Bu4(*8Kb9kIlo+|h#|HADsNE{@h3Wt zPDO?(NGNGjzSKZfnep`91rra31WiyrE0UMyunXnq;@a=zGJR zU2Qt@k@{G_Q~KHVUoU{FT3R!;;u*i<_wHXrQqw6IkIYa!i~U<* z{$6v3V6BnIHnZL?^A+~3B6AJPow9v-cSE%E1k4FRKriFxx7tNEu@)G@y+9lr`Yy-9HlsY zwAsPy@y}OR9p247Ih1wtliw3nsiQIPX`KSkI+6Z(GINR{Y%k-{S1)H}Z-1otaJ)fG z*{M~5lFn&cf3TWKmhFBEuBw?7JvMbNFHTb?-Z%9#P`M06?mBhgx68n|8 zxUQOakX-KG5`EcyV6pVjd2)VlLqkqQRfR9wcbt7q?!S)<^ZrcM-e~%n=`v@#mt=bf zjap%tVbMN+dS{_;D%Y|v=*)4JP8IkCnyTGS7i&1?q4UDOuEl%zqSw^Wo)I&bC#{cP z-k>*9z5coErh1G?0N?ZorG``@XTv}Fr4CQ;(73yMT8NyHraizDT&~R77#h(PB&Pi8 z7nxK^RQiDs?fa)nq_2fY1{IS%2+{7^rpi1J>g^D8F@Lh`mTA|M$#hA-D-MPS$^Ynz zWsXF8Qs}am8UKWi8-rnYMz2N~U)t9^Hmy?o@> z_9-;g#>UZe*Di&95jRi!Ufuh2f$q=gxw@N2geYiOZpDq%-a5N&n!S7f-y2VpNWLCB zqp&a^;t*L>S9dX^?bD@W_2#xO`;G?wq}H9eL2tbO{aXi(GlzVmqbHVsHBb-l{vegq zp7N)jj3-9*Zc@Et9Mww(v2S1Yt#6O!94WX)BcsD^5p+L3x@WvUp82ir!#5;X_og*R zr5`Un9x~6M@@DA%k*~Y1x^Xg!8{hipRsK)uqFXUCnO)IrQr>)wm0WjD9F8VUPZRf# zT5xoZO1{1~?(s;<^%71C1(f-((oyF>C<{?~N?bp9nvw2Z@@^@X28JJwAaf|6-8?5P zrzLoRa(KJ>dnI@7-{UL^JZUio-v*nth3%%PPaM~|pH+I6wwaTEk=j#&c5nC}jc12; zJUJ(I`$G(yl|$DKjiYJp8aW#x?JBd=tH`DsUgZen%Ar@ohu1^zGz5CC^9;SYl)_a2Tl{H<= zrE&U|!_xP>ys6yutzJ$l9=WMEoAc97CZt4J?F{bk#v;BM)jx3zmH$@&-7_N0>YyU9 z2J0 z8jbP>nZ-CI(MsU0#%qc6HHtDplF-)$X_}x*i%Me9f_A&ZLl3RTS%cLYVH{Y8_aKz2 z0?r8_9L8vbkYEH(XuNkj#&mUka!!&oGDMbE<4l27;H<}ahjW%R%@`S8KpLe?Zkt3~ zix(ceAkPgOAKS|IQloqw?-Y^<+qcY&&(LmfWn#-FQYmQ;3=*exd@AW#aD_p7FiNsu zWEdp`-I-~;1BXBgRlU!Sevi@t7A#qb^*Ph6F}%|lWAS*r1FH>*f`eAApfR+ZetU}c zrtL&=iOUSVa+>?r{g8BclqH88$-j4zRE>$ks<6*fd ziDa_ZqHJ}mpAXWboyU7nQAVsHk|-ln5|5zW+RpU!HV_b{a>o}sDJn~`K)0WhDnYB& zCQiW##e#SNGwmLmHf^Hc^C*#kwUoUn5?|67XrPN8)_OYq9;Sf88muuSaYPhJoKzSg zLB-VTb-eJ^)zestqK|N%B#EhI8AK7aT1J#6M3JIat0S$$7z^Gr6c3X%6x+vVz(HNq zDEbAisD5^?S-F~rHax=6$Ox+sIDjn=ZzCP9W7CL@n|@3jW#q=u99_(c0}m!yyp*)L zwEA+!S+FtEd5Uh1?H8tUxor3pz=BNhoo-bd`n$n)M4Nk!4kF@+(@QbZh+)$2$l5k}*BeVo-;W6;)Myd!lM z>m1HGWE9~97;lk6A>#;B7O14Uk;`gz0D9dHMwdj9M0kgARe;Z03sRtx80j3P)!5vV zkTT$FxFTnJYnqAi36z)AL=09VJV>7q*%aY&JO&{Oq%XkQDzi&Th$KRJgmkDVCQ1^r zG@;a%>6x5f3EC?}BuUd60*5m>=n^So5|N@JiIooH9nN~Rm1NBUTK#PpS{!%cDtg@> zn|@ppi4?5`-ia!sFBafZW?DV8&gqw)#?S(mtsG>gHBGCv9f!v_hjCRTM9K)~zl|&$>a)uj6-78JFwUZ_#+e*r z`$SPpHrOCZ6P(vbl~b<^dNUn_HpEg9%Lrl0D)4L#W!`4IU()IHh?Atspb`mjtwvT$ z$@?)$R6|2KRY7n1{;A4q5(`a%GC7CkBvX1u(Z!O9}BqK!RK`N|sXsbaA zyZ|8uUKoTFpsK7f=c?x);(`6}Zw4 zj3$<5RuB52+*dizNS$zrBw_JEulU%$V_Vr+wW5LdKf%f#XRuf!{mJpB4%W8 z7*jZ;S2)$g>l{x>b7Yvj*TV~e-7%J*V$4gmFt@3_H@Xb?nI|$*a4-QkS4>8afVrF6+x-2P7PMj!&^eEveb4|P5!RZo( zAX0)_Jtd75-V2O%M8blSc;irUM4YBXafCDx2r#9j$P1jcltqa(rV3z13RF~Ok~vqk zQ3!-mNGZ`}Nm&|Hr0_d3xs0vap@7m<`>PKQFfceozu%`QG+JwRxQWg$lL2FN`Eh?%p&>UfC z;bMk|N03rri$1>S;fn%S7Rbc0_O>|m)YK@F7+cr_=Qyj2*gcS!Yr{A5T z=*C#<0AFP~2}xFO(CM_u^Bz)3qF53&Qe-J{eTVZ7;UcW9A`?PLyth@*Z8oT;aK_VZ z7o=%K**7~f-~7(OhBKZh$}q;$ZuhWGBa|nJVpI}U!8{Mf8A?}HZHy9>iYSVbS{z|b zmqtx+z=|c5eVCc_Xnddp(UO`#( zh>b+bYAPi-{NtvELp9^E>RSiGVy#6#~>rgnkbE{^IynnOyq_nN~mQiGgEEyULWs0gCiMn+N9$_ zcY1j5!S9@2T3z>uVzNe!f#w3bErU|Bnwl>Jv64(oO>oB#{tJ>ianePp9xBo}>yU8_ z!cdw?I#Yd2dlF;YhPCiOLwV5PNIqvvhrp&uMxbA;xPfu3Y(m0_Jr}Xl? znv!ol>!3u;Ecq>WMIv!|uW)L14 zS&e#}((4xVdVLDhCyio~G$F0kaKO;uAgy*AEd-^vltyEnLkY$9sTtxlrP&-}YU5^X zo+D$4jANwrI9q-3;!A_^f>=sMnuB=nnb@=$U1%f%DI~pqzq-x~No#tBILWY5Gcvk} z!ov9Y7D!XFtijOW0DiKIavoC@=yE$-+F!q3B0giHQE`BvskH-4q_RT z#;TfHXf2ecs*@%qLP>-*cx`A54$o%Lb*3j!fYk*u1|mgX^vJRrX`@cwt+K6pJqxuA zho?5spfNZ|)~ExLPQORLo0FsoiFEYyKDNlKX{*jxePciuC0l}(d12k_n@C1|bDogTyy-J(P|s`_Jz!Z=+86HI~f;7p0r79|AUIZ9_q zGJ&iKPy!Lfc%eWjVi}`E%Ia04)M^<;QLyH~Wkkwz|GG!nw6TphlDr4rD{A!`L&Jk4 zNyN5^aoRJ}6umaWl+=_YRi32bSsD%CWDhH5h*L$YHN(tgi?WyVga5jRX0u6}E@rqn zij*E}3{GgKW+v$6T~u66W40KqE7?4?39Wnh)Z+}4Wq~9@2oJ7`_Bg7*N)*+=fe@CW zr^!3h#Bo~H{u2^os(?k3q!?{+!lR-Hyu=F!LSP&q9EG(wd{tX7N=Z~g8Yh^dhwk@i z4#ccnI?SSxL6Xq{^2LtvZ7m*sYz$EpSZ7F50nS4dV_k&9A(9k{BG3DbjvmN_ouO+j za$tmdvXIeLhqG|i8f@C3Wpf6XEhk&5C?+@IJDb_MaRWqsq$`k4BfY`dD$tN-H7ts_ zzJOZ2$>v9XMA6&Efd?-nP7H$0C~p`_5|UJLNc{jFdh9Wh!6t*lLnKK=p68@#jj@d_ z8qGSru{H{Dy1;mkz>~yP@K#BQv0}%x$|{Qt0Z}9|w!l)-%}b*AC)W|_CEg0+D50L# z8K_6-ok3e)WpWNiDcgG;Ox`8(HKeSQ#X}4?RuH8rlVe+H)B#aa_GU0vBdo@GM>DG< zr9YN(;fY7iw6`SyF*G+r{;OsFLm;X8O=5cwL-kr5UyOwhe; z6si!4ApBCtscf|thO+@g>84V{QHiI9RwNTjR^kUQrn z^PL`mW-1q4EX(IwUya0ctD zM#KXdQ9VPZF-{1q)fla5)*}`!9AsoTVbMsP&0`xWiY`gIl!Xh2n4B142@Z_iynEQ>GxaMvWM~(D-_vKlUh^JE4tXiQmtnK4vjCUR<4BGXvdBH#ygIJ99T{CaI#{WgC{Bn~4J9UW;5k(P6oRB0LWvQ#oI0VLM4As~Y&LahsI>%a%h+=BB0h~7!WlokR#1R(gA2kgwmwkw>m#)tV~X@6o$9f}TOR9(V(`I=6(eGzSOi^bNhDLp`do zAXDh64g;wrFD5|bB+5}s8_Y~;CMLHs-RYBSjVt@ez9RJkZ40Eg#5F-=Q$|J?P_$a; zp2k^-RE!lGXB>^j0AmE2wNbXrJknvQscEFQ4vYK#$iVs zikF_P;~Nm7s>kLX;Ho-w0*QQ*`@y9L8A|E?I!J znr-6~m{M2!MPj{2d4X~ug&@LVq{VnuUF)2}<`(Y_&N||HjKHA;2jG zI3e&3l<-KGBaI+&7U}!cMNAZVylJCF^<_ePW;5IGE|G2!S0-qu$OeWPX)eO`OUizi zyzC)*)6{Dv#&?jm%gW`W6r@laCapE;cT0-t4!IC$N`y(tvSCJ+Wn|rHilSH5=2H=o zjH()o&M`DHLe{7;x@-}f#~vf^w5cT#LPQj~LF)pcs_Dhr80vA1j1)z`q@HDDNknUA zhB!?a9vs9L9g69K(rSnlQaE~fA8#EZQm9PP%R9(I5D91w4B(B!=LM$Ir5>jk=ds=* zoyWSW%t5cytpdDhiYqlv8~RdFZw?~T7{BAoC95?GfhkJzPFanQ9rXnx2TB{F$W*~~ zbJC7#62D3kZIMeDr)hpGzv??`!Z-Xn!T z2uUObUaHx8Zlab#ByrB8>M8U~tTg0B3zwJpDnf#{;H^i<2)xDWs(`=4;)EmG5kyhm z;f=u-IsLAro+d09S-{NXINl4KmpD5&L0t7)OWs_YcW2$G1A(Pc!jWM*odRK(aEw)Ok8HWX~x-l0|# zEL^YvB_v5BWu&$k5j*-0(t5;_p+U4!XzQ^?VCiAYKAqke)>oOH4?g-o40L*|I^txq zWPq{FW7O;njp9+-?KayUdx+F^aW==95=2SlO;u~n#26jntiij2qO%#@D9OtSB4voW zF_BO3PB1aup~wYAX((|-Sxjf9L%-iAPOItR+s9krBeFE1F;JtPN{U`VucIjogOG|i zju4r{_B1NW@Wx?XiE|}FXuK%!N+XoT`6`=GB4?lcim}yWJfvcHXfeHRi?S@qbB&V_ zXDO{#2V*m`MiW$oGc8Q+z~>}!g4X|kduO)e*p+7I@88ooheM2+l2Ws}s@es+4fw`} zfxfT-zqJ9ww}xTBcEi|r zy&|kv9QOyxenwKpSS4xVPBN?QfR;>(1;JyzrD#F5U@&+TW6B9!5>7woV2i;?Kx3iU z1+*>R2`$0tyhXKDD`Q+Jd1=O6J5}4cL`CBrRd)of)S8iEQQHA+SFxhlPB_kQx%>7d zT4%hA;DO*#POutlFcH$+_Q^yGxwrHy!MVyJm68kIWPCXga5&$w`S5d|eD!O(iw_y6 z!uz+cdGYOUIX*nc%}0V6InE;zIv%FTWDi`rOYSDeVxMyL*)3`;aXxVL!g>pC*f8(k zQN}%Kp6RsWy)@%JCaNe8507}?@$92dSf&}{d)618`8ZK>0#h)i;)7#+eBg2SKoFrg z%XHkMC1QveE7-oHrc5=0{#fzXmP8)3pfPfBeZ|^&-oJgrwBIv?fx$L7Pn;JDiVFb~ z0-Iq)l?oOTm0A^-8-z&KJ=z73N{N}PC%5Ej=6ISTaMoi^rdzFGW5L-9WHh!EX3iD7 z2dSuu%*(|3;+kY4o6Bqd@?ZWNe7EKKn|JgV*W5n&fSV6L$NKZ0+gWFwb=E(3olUp1 z&N}O?pRru;Fw)4S#u`j6hzPl4M1`U6{?VUbmZkkh+AmsiOMqD4>W`<-8EX)0Q4yR0 zZw=}l1;tdbV(7h)M`3(?B$e3cp|ujMCG>&N4=8PSC&fgq6;v_h$DJ=lYbH%2yW=C? zfbRm+GEzlwgTq?zV%V$(E-$v^xUhe`$5@LN!P<`P)h+$yC3f9mhaOX6+g>vrSC^N> z#{*-|C-bYGUeF582d=goQqG7mSYxTVVvJC8Zt1_yv7}7Ql_kz?P+e%|r3$7BwhHw` zi7h$P4IK!?5>Hgof~l!(8r1-0W}YK)TJYj96p+F^?iq7pJ{%}HAqGNk`TgJh3D;Ss zW7|Npwj~gaX&ZObcw~M&ka9t5`~HIssK6mcf>_d07!Qf*{wlk<=Mdl1uR6NHVYILrR;(`u#0fqGjH{SgsWzjg-LG=GZ*$Gm1X|iL z7wjK~z0b+GEX{im`@a@F!^Bb|^LS+NiqXt`SO~@mT?Z-@GBld#owglCV#bCM)(>Y2_SW#>hTyc2o5fWfn4trFU zTq0c<=vQ6azcU@H?FKQ5?<~|zu9Z>~g@|YZDV&DYkSZYBOd$iYEJ&`Wl&2x|fm9s@ zC{o!yJmBYo?FLXGbOzg5*4t~2hmpto1B368VyIYhX>{bA3RXQBWl0P3{>Z~`e~&eZ z-Ucv+6f4A5_Y?c0g;kj1g2oILjImS$)fmKf#8`<_q0AL`+O@2rl*8D7Eu}&gL>gn(Gn>oW5i>cXOB@ZVvwwq7|E$16mqJFSiA)zN+L5J zc34eVXQ>#*!#%Hm_<>b)Tz~jEpI%+^};qJ|MM3d1FQFj10BPO=%R}!j)QZn;gh|2*Zkms4{I8jX_ zQ+tIRXJXCRRlt@^OqpN>of|z^V`QI>Xx-3R&#+lhV`fSb-#LP_l;jx zZ`+&m%sem6m~(;S(vpw;YR!6k$<6I8)>+;?yd!I&RK;QFf}`s@hU;sxGvpXC%sjk* z&;I?6JV%1@?Y#eSYY=T9qLQ#lsHNgWDViv;kg3phbbW_P<+Cq8;iFG}!Ntu5-@kmv z+xz!~tUSB8CTnJ%AAvK$^sKYaI_saa&ZgU0XPtG{&sf{r8$_g4Nn=D@+jUdxFy2yA z#`}PCw(XUnr=fgsa!Rt9wkalJvB6VSD7BDt!BUBbJ(^=1v(|#Kt{Fcxlf-sw=4yyB zVtlKBQ4@m0g@JKtwKr=l#t7EiMxe}Xv&WRu(s=@Ep78)13ao>$pGHz# zFa)X<#u7QCnboT2=H?P7P*mB^N51~!OA?+>KKz7Xz2@bsN0vAdd^0PGT5`64!%726 zj4`1B?8;G=LYWKXX4bV7QQ`8s=lW_*iiNwo13C8G?cQ?p6c+oQMINBb{OVu*1;6;^ z7i}}{cx1Ob@Z!6-*f4PO^h2qg4PbnVq3Zh}KmN-rvb_=fSsRbs}3_US{LgwAW1H*R3)2l0{hew>x zeEzRL=7-%&g4xpE{$A?J!W4!^o0=S+;5u2y_`Aiez!-yeTqS|(~b(i_-N zvD!?mY!lvOmL<~p&`79mP0f|0N4j8mdUM?>qFE@eV|iF8sj#F-EQ;?uS(I7?ONY)a zWtkU)+JYFt8VfZsr-Z|iXN$3GF0TfzF0Wapkr?+lXV^bJFfEbLIdB#03^{Y!4z#!v z(4b01C^+LGf~gs?P*j+skgAZf;)Y9#R&q5gB@!QB(ruOg`Waz8P~RtxhkK+{g0XaA z(~^(YAhuO*!Lp*t!7IIFc(>YvG)XIsp*-Uw39Jy9fEf^CJ>!_`G+<*(I{cU3e>pWru)hIbtjGAWh zpw0Lz+6*Ns`^O{e>or%mH`M$#>XFi%J$-hNapFYUlN8j%RCbtJpK41yw6B{;O^x+Y*oAqjOBqCX9R<@ zo?IG;CdQGJ7OJ+?qG}=4gg8f)iZzNAgSQr}VLu)S&L9-F7XxAktTT{dd_0g-Caee6 z-3qK_kw{II>EXZ+kAGy&32_!TbZnp8P_%MbW^xC^uqC!Dh@0mIcd^2nX!oXPtG{SwBOaO}Deo zI_s>Tv8q6dExl&FL#$~gQz@tYvbK*^N+yJWa~@;b=Twy;ctjjv5bH43Q4n%9*iy*L z3?;WT!Tvz472_?&IYbLK<&&|}QYrwY6l%?+)b_ueF{m-5oG`|)YXuQO!(;9pmnvJS})@@!q!D zr15n9C2Cr3$o8J0TeZq7(zd5+ZPdS#5DXZ>IoI~>T)_8%QWEhPc{uEt)52!(Y_}V7 zN$el@h;P(SRH&uZb~l6L)2AIDe{_N2lCl`yynf)D?_MBrVbwXVt~P8}fpPypX9AZ` zK5O(?V|l+jvMhyMm6!`Yc!u?c(DxjVk!egnRdZdx#tgO*WOJb=!QfEM80Y!u*;CeQ zN1BgdGMJfhT=?#XZ#d>1eh}7Ip12bZ^PZP)pL2Kjju;EeT=3qr+N@hbs?Mm*gx=Fx z3rUGN{Ztj@w8JINVWq>kgxHEvrBG2TRGXPuPzxmp)f%oZZ+Lq10l{~q(Xjh|BJGrc zj+&uUhq0b%`H|e)%+4Z;Qu+S*OX9Tf@slgo8_)H#p5gP)`0nd>6r_w+P>FmqtLM6%0k&62m;PKIv*HJpyY+Aj4UZ3L2&(+;C-w1rb^L>HS-6SmbS;}3AmHdW>8X`nc{(5BF;LZLf3oN{g$r3AkC4tuiv6| zY&C8z7%9};Ol_)geRW?FG?e=SJb=jh=Ld>aJ?j2%Xd*eVrYNK>E1yWSzm{DtSeZYH% z5jhc&E2XwgOse_E2fVgzOeaEc9*mGl#G{f{wELMPc6zWDmf}SPXuQ`GtOH| z$&i&a=eC`x@VL8YiIG?mqCxH&h_>|GGSAr1?k6!0J9LO`+kz_Xu@(A&woyc;c_cWG zIB99VeIm<1c0JwYz`NHkIZTPwu*O=8tqU7xxqtB;&!2z8vh0EX^k;pZaMoF8opsjF zTW8blth3HK>u0R_a3q(45d*yX5>H&7%Qks zODk2y*_ND3|4%AIiXx(j)>DnO8F)w|d)C^jUL`G96^u1p++5NPJzACByLXga=t4)g z-hi`IE2vbK@kks;Oo|v$vRe8+v>l}ssTI5**j`*BqNEgA4W2pfd7SR)hn|`f>F`Kh zMmir*RZ7XklyD9<7q=~C<1A*irIcpCfKUrmEu^#nf_H5X%Bc{#t|jzhVyT(Kct9>4 zS5Hj&8fbcAnmL(zc^F}AL`W9`-+cR=KYsfi0$6KFsgg^@^_Gus zpVDs(uiw6>ESajyX_~*FXoE|%wkb+Qj3Un9e5*uOf#NLDDcLLCmE-E!mhH_Y%RaM< zBb!Ul^{3ZFgy@x!)|5qwb36e8GJ2}B`u=xH1uAjXLA0?s${<9<1SD%pC9@!VWrVT_QMBY9b< zQ3(}#AMh$1?_i-zpU&w!t_O)S9Ta zP;DWST5Xw($#dI@tQlm%*?^YZHYja}6+>ahD6F~-+w~>K$Ax97l$@K>SHP$TXHXG# zyB*dTLf2tTOK28>Vk=n-B4Dkdp0+9*>RB-=2)>Q$w>3D7`3KWYQK|}JOoKM4p`v6> zq%OV4k^eg(qjD7xl-oF0>zxkU#(btjP{SRcDU~qVE+Zz{o&%>+VvcyEJ1?xP=@dyZ0 zDL4unCzL$mkFdGe&~4U?-m+P5oAH)1IYmsiSYs(wsVTK&Ep4s<;Qs!etOiMr)%F^7 zmgBg?`#|U|({eoI-{HHC<2>`h^%b9e@f9^!?(Rok|NZY7=fr;ho=-md z@Xzto;HSY;vQ z1tShy>`!$jJwX>hvEHGj;tZrxu%;!yeoVC~t{H5(K-ET0wT4>CX*Vp>`8C$Lwvpx? z*S9w;$BA)@m}S9AhwFN5FjyvaDk3N0Io829+ z-n_wNASHY);2b`9sw%3L&RVwHfvyjTRIKy#>jANraep9>b2ART;Jl^gLP@C^jVIF} zE=x;sIc0q{u(}+e6z2Ve!BQ1$Y1F}ap4<1CBdZI~(1Vwmn~M!|Jn*-_`-Z=N{*tT~ z(|Ocq_UVCYpYqw4AF{iD0`dxg;yhDHB#5zFz}|<8ee#HroNJqGB*U5KF>U z!B~5$+ZA%oc-KDm`0z+vMn3uY1N!wqHy$vDktL0&bUc0Xgk>o_+>aRPDWzat+Yi(_ zBU(7@j(q=z=X~(_r+o74GoF9{g5zN%r~|7MHBeg(8L8wFDVhsyA+7UwJ_FYF&4D071;cpI=rGSsEJ%E4@I#Bd~HAfF=fGIY*`?VNKil>xUK2!@L->F63A^ zmXXe{`1IK)c<*`p<_)jkyroLU8adT<4K)|yVFcBdQq;DSrrtDCX_3=;R!=4yIBh$F zKc17dK+$yCbOWu;02c%`Le*wMTPsvm=4HYJVI6!+$w`C6NaYlj6F8ZZoNCxLMJQms zAyXJlBzVCVi>Zb_46Ig{i0$$HiVN@Yd_Y+rdA2FYvSYgcftPo8%wxjhxip4Vusr+V zDdTwLI2~DUH^kI&I2_Pwk*Z9G15z__irkGyG#Df}bWlvNq$N>RvB6=hqn3*0A4u74 z&y%0(zw5Q@IY)99vDKVneAu-dPwk?;%V7((!+q{Bi z)M(pk1H1?^MqyRf^f%vLRHu}frbN|@_sv8SX%(}oi6vH~ z7+iKZ)66xCk`9Nq|7BZ(t@Dnw>hK0?D$L75N)=-=F;xJV#s|mtVvTbxty2s*qg1U>GV?TIl$LDUtnk(|9VX(kFikU^ z_Kn`^9BLt#g@^r!Lm^kh_uM@FfI4N~K7WN@7jF-I{l}Ng zRVdyODQrI8^6P*13%>aLW7y4n@x`Ys(XgKj>(vHPM>K^}6166_>kC$WPo77pk+@9w z;JLcJ;PJTd_WnIN6-sQiUW_4^)KX}swmq>W5e7>L7BR|tJ@DCQpK)<{$%`M}p*dnI z7=aQqSc_w52E=H&-$x$be9!uJi|GdJV2QPmBr^DbiIyB|tC|``rKP)SR+d_rYQl7m z%cmEZTSr}2(xB|?k^A`(>m0TkcCYT(f3ssV4HyxubF5dFEaL(a>4%O`g(XgCE?BXc z3~A1|x*_hA|Nfs}^ZUP<`RZ36W8DhT#BrHfrjc3-H@8n19~bgmNV+g|9n(@7=Lwvl z?*}TCB}Q<9?>*U8j?0JykI9bl@JLFfc}sJ|_W*_|PsF7WWz{-Lj>Md?r`j^s66cmC z_0|!LBTf@zUhuA??|Zm^Rj6jy^aj%8^ZW*{8LrO_h+)KH}$ zR=^p=8R#unpIqY|On3Jj-|tCNC8Z32HI*l~x9r}(Cr*iA1EzX(p3$60GB(sGmYTtw zs_vx}kd}}%=0r%=W@2k?&=L1QBB> zrTx3Swe+^ft79CGG}$hD$4T(>5KuAOx@k(Zg2Dx9`0$8lmD7o<9j`c~1z zA=*;LT5~husAy@1Z;q)McoM1EqN{+WOqwdyd)~eIdmg63&8zPjRx4J%BIAL-`Q7gs zcP}Z+9*Hw645ZBR{tn#-*3#;+(?0UBdqp<{QeH@@{of)A#&>kCM;aanP|`TD944y5 zl4g`j2%egoc`8UdUui~o=Q?h$t`KAS`nUhHCDpW$@=Pi-VenWB&KbJCW0@CJwVCTb zl5k6DwPdZ0svHYGtmylJhw*@la@-#{mc+0A`rq?c|KYFkS6lwufBCPx{lgz|wA)yw zvAwZ!`Z-J`;k-j@B4+4@9@H?V1w@fP(D^{jm7(tuQ=r0jv*GE7A5co+@NgiNM^rM- zRW|*aF09z?k9_;}?{Hnuu(}|Go~PGq-o0D6yj=ZxpCp`h)>&tr^|ROi1 verifyImageCopiedToClipboard(String assetPath) async { - final imageBytes = await loadAssetFile(assetPath); - await QuillNativeBridge.copyImageToClipboard(imageBytes); - final clipboardImageBytes = await QuillNativeBridge.getClipboardImage(); - final pixelMismatchPercentage = - await compareImages(src1: imageBytes, src2: clipboardImageBytes); - expect(pixelMismatchPercentage, 0); - } - - await verifyImageCopiedToClipboard(kFlutterQuillAssetImage); - await verifyImageCopiedToClipboard(kQuillJsRichTextEditor); - await verifyImageCopiedToClipboard(kFlutterQuillAssetImage); - await verifyImageCopiedToClipboard(kQuillJsRichTextEditor); - }); - - test( - 'copying an image should return the image that was recently copied', - () async { - final imageBytes = await loadAssetFile(kFlutterQuillAssetImage); - final imageBytes2 = await loadAssetFile(kQuillJsRichTextEditor); - - await QuillNativeBridge.copyImageToClipboard(imageBytes); - await QuillNativeBridge.copyImageToClipboard(imageBytes2); - - final clipboardImageBytes = await QuillNativeBridge.getClipboardImage(); - final pixelMismatchPercentage = - await compareImages(src1: imageBytes, src2: clipboardImageBytes); - expect(pixelMismatchPercentage, isNot(0)); - - final pixelMismatchPercentage2 = - await compareImages(src1: imageBytes2, src2: clipboardImageBytes); - expect(pixelMismatchPercentage2, 0); - }, - ); - }); - - group('getClipboardHtml and copyHtmlToClipbaord', () { - test('copying HTML to the clipboard should make it accessible', () async { - const htmlToCopy = - '

Test Document

This is a sample paragraph with a link and some red text.

  • Item 1
  • Item 2
  • Item 3
Footer content here
'; - await QuillNativeBridge.copyHtmlToClipboard(htmlToCopy); - final clipboardHtml = await QuillNativeBridge.getClipboardHtml(); - expect(htmlToCopy, clipboardHtml); - }); - - test('copying HTML should return the HTML that was recently copied', - () async { - const html1 = '
HTML
'; - const html2 = '
HTML Div
'; - - await QuillNativeBridge.copyHtmlToClipboard(html1); - await QuillNativeBridge.copyHtmlToClipboard(html2); - - final clipboardHtml = await QuillNativeBridge.getClipboardHtml(); - expect(clipboardHtml, isNot(html1)); - expect(clipboardHtml, html2); - }); - // TODO: See if there is a need for writing a similar test for getClipboardImage - test( - 'getClipboardHtml should return the HTML content after copying HTML, ' - 'and should no longer return HTML once an image (or any non-HTML item) ' - 'has been copied to the clipboard after that.', - () async { - const html = '
HTML
'; - - // Copy HTML to clipboard before copying an image - - await QuillNativeBridge.copyHtmlToClipboard(html); - - expect( - await QuillNativeBridge.getClipboardHtml(), - html, - ); - - // Image clipboard item - final imageBytes = await loadAssetFile(kFlutterQuillAssetImage); - await QuillNativeBridge.copyImageToClipboard(imageBytes); - - expect( - await QuillNativeBridge.getClipboardHtml(), - null, - ); - - // Copy HTML to clipboard before copying plain text - - await QuillNativeBridge.copyHtmlToClipboard(html); - - expect( - await QuillNativeBridge.getClipboardHtml(), - html, - ); - - // Plain text clipboard item - const plainTextExample = 'Flutter Quill'; - services.Clipboard.setData( - const services.ClipboardData(text: plainTextExample), - ); - expect( - (await services.Clipboard.getData(services.Clipboard.kTextPlain)) - ?.text, - plainTextExample, - ); - - expect( - await QuillNativeBridge.getClipboardHtml(), - null, - ); - }, - ); - - // Some platforms such as windows might include comments/description - // that can make the HTML invalid - test( - 'should return valid HTML that can be parsed', - () async { - const exampleHtml = '
HTML Div
'; - - await QuillNativeBridge.copyHtmlToClipboard(exampleHtml); - final clipboardHtml = await QuillNativeBridge.getClipboardHtml(); - - if (clipboardHtml == null) { - fail( - 'Html has been copied to the clipboard and expected to be not null.', - ); - } - - bool isHTML(String str) { - final htmlRegExp = - RegExp('<[^>]*>', multiLine: true, caseSensitive: false); - return htmlRegExp.hasMatch(str) && str.startsWith('<'); - } - - expect(isHTML(clipboardHtml), true); - expect(isHTML('Invalid'), false); - }, - ); - }); -} diff --git a/quill_native_bridge/quill_native_bridge/example/ios/.gitignore b/quill_native_bridge/quill_native_bridge/example/ios/.gitignore deleted file mode 100644 index 7a7f9873a..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -**/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/quill_native_bridge/quill_native_bridge/example/ios/Flutter/AppFrameworkInfo.plist b/quill_native_bridge/quill_native_bridge/example/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 7c5696400..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - 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/quill_native_bridge/quill_native_bridge/example/ios/Flutter/Debug.xcconfig b/quill_native_bridge/quill_native_bridge/example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index ec97fc6f3..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Flutter/Release.xcconfig b/quill_native_bridge/quill_native_bridge/example/ios/Flutter/Release.xcconfig deleted file mode 100644 index c4855bfe2..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Podfile b/quill_native_bridge/quill_native_bridge/example/ios/Podfile deleted file mode 100644 index d97f17e22..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Podfile +++ /dev/null @@ -1,44 +0,0 @@ -# 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/quill_native_bridge/quill_native_bridge/example/ios/Podfile.lock b/quill_native_bridge/quill_native_bridge/example/ios/Podfile.lock deleted file mode 100644 index c3c4a55f2..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Podfile.lock +++ /dev/null @@ -1,28 +0,0 @@ -PODS: - - Flutter (1.0.0) - - integration_test (0.0.1): - - Flutter - - quill_native_bridge_ios (0.0.1): - - Flutter - -DEPENDENCIES: - - Flutter (from `Flutter`) - - integration_test (from `.symlinks/plugins/integration_test/ios`) - - quill_native_bridge_ios (from `.symlinks/plugins/quill_native_bridge_ios/ios`) - -EXTERNAL SOURCES: - Flutter: - :path: Flutter - integration_test: - :path: ".symlinks/plugins/integration_test/ios" - quill_native_bridge_ios: - :path: ".symlinks/plugins/quill_native_bridge_ios/ios" - -SPEC CHECKSUMS: - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 - quill_native_bridge_ios: 277bdf5bf0fa5d7a12999556b415a5c63dd76ec4 - -PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 - -COCOAPODS: 1.15.2 diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.pbxproj b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 8bf4b02fa..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,728 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 04485D02B3ADDEE8ED196D55 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9AC6E698DD4A34C635BA448 /* Pods_RunnerTests.framework */; }; - 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 */; }; - 5F41E8A1237947A9C892201F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 409E53903E588B6FEB995A19 /* Pods_Runner.framework */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 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 */; }; -/* 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 */ - 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 = ""; }; - 409E53903E588B6FEB995A19 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 443FCBF08FBB508D6CF6608C /* 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 = ""; }; - 5D0CE3E32781BAA4A6715A5F /* 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 = ""; }; - 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 = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7EFE106A355305B732F1B40D /* 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 = ""; }; - 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 = ""; }; - B0AB33027B06A6717E4EDF35 /* 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 = ""; }; - B3D9E0EEDA4BC0EECEB6F86D /* 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 = ""; }; - E9AC6E698DD4A34C635BA448 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EB9FEAE97A5BA3ACE19C6E4B /* 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 = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 607C6758DA859CCA786561A0 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 04485D02B3ADDEE8ED196D55 /* Pods_RunnerTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 5F41E8A1237947A9C892201F /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 311C8597AB96BBAC4CA75E79 /* Pods */ = { - isa = PBXGroup; - children = ( - B0AB33027B06A6717E4EDF35 /* Pods-Runner.debug.xcconfig */, - 443FCBF08FBB508D6CF6608C /* Pods-Runner.release.xcconfig */, - 5D0CE3E32781BAA4A6715A5F /* Pods-Runner.profile.xcconfig */, - B3D9E0EEDA4BC0EECEB6F86D /* Pods-RunnerTests.debug.xcconfig */, - EB9FEAE97A5BA3ACE19C6E4B /* Pods-RunnerTests.release.xcconfig */, - 7EFE106A355305B732F1B40D /* Pods-RunnerTests.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 60298DE18DD2259B1F70E16A /* Frameworks */ = { - isa = PBXGroup; - children = ( - 409E53903E588B6FEB995A19 /* Pods_Runner.framework */, - E9AC6E698DD4A34C635BA448 /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - 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 */, - 311C8597AB96BBAC4CA75E79 /* Pods */, - 60298DE18DD2259B1F70E16A /* 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 = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C8080294A63A400263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - 9CE04CE57D6F8D16DB16440F /* [CP] Check Pods Manifest.lock */, - 331C807D294A63A400263BE5 /* Sources */, - 331C807F294A63A400263BE5 /* Resources */, - 607C6758DA859CCA786561A0 /* 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 = ( - D348EA123CF19995BC7E5EF3 /* [CP] Check Pods Manifest.lock */, - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 930CBC47A47AC491416D4034 /* [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"; - }; - 930CBC47A47AC491416D4034 /* [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; - }; - 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"; - }; - 9CE04CE57D6F8D16DB16440F /* [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; - }; - D348EA123CF19995BC7E5EF3 /* [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; - }; -/* 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 = dev.flutterquill.quillNativeBridgeExample; - 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 = B3D9E0EEDA4BC0EECEB6F86D /* 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 = dev.flutterquill.quillNativeBridgeExample.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 = EB9FEAE97A5BA3ACE19C6E4B /* 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 = dev.flutterquill.quillNativeBridgeExample.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 = 7EFE106A355305B732F1B40D /* 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 = dev.flutterquill.quillNativeBridgeExample.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 = dev.flutterquill.quillNativeBridgeExample; - 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 = dev.flutterquill.quillNativeBridgeExample; - 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/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a62..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5e..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index 8e3ca5dfe..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc14c..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5e..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/AppDelegate.swift b/quill_native_bridge/quill_native_bridge/example/ios/Runner/AppDelegate.swift deleted file mode 100644 index 626664468..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -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/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fab2..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "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/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e9b0ddb1deab583e5b5102493aa332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10932 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 diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1674 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 diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2fd..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "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/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725b7..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# 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/quill_native_bridge/quill_native_bridge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c7c..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Base.lproj/Main.storyboard b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c28516f..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Info.plist b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Info.plist deleted file mode 100644 index 629de38ee..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Info.plist +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Quill Native Bridge - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - quill_native_bridge_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/quill_native_bridge/quill_native_bridge/example/ios/Runner/Runner-Bridging-Header.h b/quill_native_bridge/quill_native_bridge/example/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 308a2a560..000000000 --- a/quill_native_bridge/quill_native_bridge/example/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/quill_native_bridge/quill_native_bridge/example/lib/assets.dart b/quill_native_bridge/quill_native_bridge/example/lib/assets.dart deleted file mode 100644 index 8cdd1aeb4..000000000 --- a/quill_native_bridge/quill_native_bridge/example/lib/assets.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:flutter/services.dart' show Uint8List, rootBundle; - -const kFlutterQuillAssetImage = 'assets/flutter-quill.png'; -const kQuillJsRichTextEditor = 'assets/quilljs-rich-text-editor.png'; - -Future loadAssetFile(String assetFilePath) async { - return (await rootBundle.load(assetFilePath)).buffer.asUint8List(); -} diff --git a/quill_native_bridge/quill_native_bridge/example/lib/main.dart b/quill_native_bridge/quill_native_bridge/example/lib/main.dart deleted file mode 100644 index 19d938cc6..000000000 --- a/quill_native_bridge/quill_native_bridge/example/lib/main.dart +++ /dev/null @@ -1,282 +0,0 @@ -import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb; -import 'package:flutter/material.dart'; -import 'package:quill_native_bridge/quill_native_bridge.dart' - show QuillNativeBridge, QuillNativeBridgeFeature; - -import 'assets.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('Quill Native Bridge'), - ), - body: const Center( - child: Buttons(), - ), - ), - ); - } -} - -class Buttons extends StatelessWidget { - const Buttons({super.key}); - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - kFlutterQuillAssetImage, - width: 300, - ), - const SizedBox(height: 50), - ElevatedButton.icon( - onPressed: () => _onButtonClick( - QuillNativeBridgeFeature.isIOSSimulator, - context: context, - ), - label: const Text('Is iOS Simulator'), - icon: const Icon(Icons.apple), - ), - ElevatedButton.icon( - onPressed: () => _onButtonClick( - QuillNativeBridgeFeature.getClipboardHtml, - context: context, - ), - label: const Text('Get HTML from Clipboard'), - icon: const Icon(Icons.html), - ), - ElevatedButton.icon( - onPressed: () => _onButtonClick( - QuillNativeBridgeFeature.copyHtmlToClipboard, - context: context, - ), - label: const Text('Copy HTML to Clipboard'), - icon: const Icon(Icons.copy), - ), - ElevatedButton.icon( - onPressed: () => _onButtonClick( - QuillNativeBridgeFeature.copyImageToClipboard, - context: context, - ), - label: const Text('Copy Image to Clipboard'), - icon: const Icon(Icons.copy), - ), - ElevatedButton.icon( - onPressed: () => _onButtonClick( - QuillNativeBridgeFeature.getClipboardImage, - context: context, - ), - label: const Text('Retrieve Image from Clipboard'), - icon: const Icon(Icons.image), - ), - ElevatedButton.icon( - onPressed: () => _onButtonClick( - QuillNativeBridgeFeature.getClipboardGif, - context: context, - ), - label: const Text('Retrieve Gif from Clipboard'), - icon: const Icon(Icons.gif), - ), - ElevatedButton.icon( - onPressed: () => _onButtonClick( - QuillNativeBridgeFeature.getClipboardFiles, - context: context, - ), - label: const Text('Retrieve Files from Clipboard'), - icon: const Icon(Icons.file_open), - ), - ], - ); - } - - Future _onButtonClick( - QuillNativeBridgeFeature feature, { - required BuildContext context, - }) async { - final scaffoldMessenger = ScaffoldMessenger.of(context); - - final isFeatureUnsupported = - !(await QuillNativeBridge.isSupported(feature)); - final isFeatureWebUnsupported = isFeatureUnsupported && kIsWeb; - switch (feature) { - case QuillNativeBridgeFeature.isIOSSimulator: - if (isFeatureUnsupported) { - scaffoldMessenger.showText( - isFeatureWebUnsupported - ? "Can't check if the device is iOS simulator on the web." - : 'Must be on iOS to check if simualtor.', - ); - return; - } - final result = await QuillNativeBridge.isIOSSimulator(); - scaffoldMessenger.showText(result - ? "You're running the app on iOS simulator" - : "You're running the app on real iOS device."); - break; - case QuillNativeBridgeFeature.getClipboardHtml: - if (isFeatureUnsupported) { - scaffoldMessenger.showText( - isFeatureWebUnsupported - ? 'Retrieving HTML from the Clipboard is currently not supported on the web.' - : 'Getting HTML from the Clipboard is not supported on ${defaultTargetPlatform.name}', - ); - return; - } - final result = await QuillNativeBridge.getClipboardHtml(); - if (result == null) { - scaffoldMessenger.showText( - 'The HTML is not available on the clipboard.', - ); - return; - } - scaffoldMessenger.showText( - 'HTML from the clipboard: $result', - ); - debugPrint('HTML from the clipboard: $result'); - break; - case QuillNativeBridgeFeature.copyHtmlToClipboard: - if (isFeatureUnsupported) { - scaffoldMessenger.showText( - isFeatureWebUnsupported - ? 'Copying HTML to the Clipboard is currently not supported on the web.' - : 'Copying HTML to the Clipboard is not supported on ${defaultTargetPlatform.name}', - ); - return; - } - const html = ''' - Bold text - Italic text - Underlined text - Red text - Highlighted text - '''; - await QuillNativeBridge.copyHtmlToClipboard(html); - scaffoldMessenger.showText( - 'HTML copied to the clipboard: $html', - ); - break; - case QuillNativeBridgeFeature.copyImageToClipboard: - if (isFeatureUnsupported) { - scaffoldMessenger.showText( - isFeatureWebUnsupported - ? 'Copying an image to the clipboard is currently not supported on web.' - : 'Copying an image to the Clipboard is not supported on ${defaultTargetPlatform.name}', - ); - return; - } - final imageBytes = await loadAssetFile(kFlutterQuillAssetImage); - await QuillNativeBridge.copyImageToClipboard(imageBytes); - - // Not widely supported but some apps copy the image as a text: - // final file = File( - // '${Directory.systemTemp.path}/clipboard-image.png', - // ); - // await file.create(recursive: true); - // await file.writeAsBytes(imageBytes); - // Clipboard.setData( - // ClipboardData( - // // Currently the Android plugin doesn't support content:// - // text: 'file://${file.absolute.path}', - // ), - // ); - - scaffoldMessenger.showText( - 'Image has been copied to the clipboard.', - ); - break; - case QuillNativeBridgeFeature.getClipboardImage: - if (isFeatureUnsupported) { - scaffoldMessenger.showText( - isFeatureWebUnsupported - ? 'Retrieving an image from the clipboard is currently not supported on web.' - : 'Retrieving an image from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', - ); - return; - } - final imageBytes = await QuillNativeBridge.getClipboardImage(); - if (imageBytes == null) { - scaffoldMessenger.showText( - 'The image is not available on the clipboard.', - ); - return; - } - if (!context.mounted) { - return; - } - showDialog( - context: context, - builder: (context) => Dialog( - child: Image.memory(imageBytes), - ), - ); - break; - case QuillNativeBridgeFeature.getClipboardGif: - if (isFeatureUnsupported) { - scaffoldMessenger.showText( - isFeatureWebUnsupported - ? 'Retrieving a gif from the clipboard is currently not supported on web.' - : 'Retrieving a gif from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', - ); - return; - } - final gifBytes = await QuillNativeBridge.getClipboardGif(); - if (gifBytes == null) { - scaffoldMessenger.showText( - 'The gif is not available on the clipboard.', - ); - return; - } - if (!context.mounted) { - return; - } - showDialog( - context: context, - builder: (context) => Dialog( - child: Image.memory(gifBytes), - ), - ); - break; - case QuillNativeBridgeFeature.getClipboardFiles: - if (isFeatureUnsupported) { - scaffoldMessenger.showText( - isFeatureWebUnsupported - ? 'Retrieving files from the clipboard is currently not supported on web.' - : 'Retrieving files from the clipboard is currently not supported on ${defaultTargetPlatform.name}.', - ); - return; - } - final files = await QuillNativeBridge.getClipboardFiles(); - if (files.isEmpty) { - scaffoldMessenger.showText('There are no files on the clipboard.'); - return; - } - scaffoldMessenger.showText( - '${files.length} Files from the clipboard: ${files.toString()}', - ); - debugPrint('Files from the clipboard: $files'); - break; - } - } -} - -extension ScaffoldMessengerX on ScaffoldMessengerState { - void showText(String text) { - clearSnackBars(); - showSnackBar( - SnackBar( - content: Text(text), - ), - ); - } -} diff --git a/quill_native_bridge/quill_native_bridge/example/linux/.gitignore b/quill_native_bridge/quill_native_bridge/example/linux/.gitignore deleted file mode 100644 index d3896c984..000000000 --- a/quill_native_bridge/quill_native_bridge/example/linux/.gitignore +++ /dev/null @@ -1 +0,0 @@ -flutter/ephemeral diff --git a/quill_native_bridge/quill_native_bridge/example/linux/CMakeLists.txt b/quill_native_bridge/quill_native_bridge/example/linux/CMakeLists.txt deleted file mode 100644 index 3d327a923..000000000 --- a/quill_native_bridge/quill_native_bridge/example/linux/CMakeLists.txt +++ /dev/null @@ -1,145 +0,0 @@ -# Project-level configuration. -cmake_minimum_required(VERSION 3.10) -project(runner LANGUAGES CXX) - -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "example") -# The unique GTK application identifier for this application. See: -# https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "dev.flutterquill.example") - -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. -cmake_policy(SET CMP0063 NEW) - -# Load bundled libraries from the lib/ directory relative to the binary. -set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") - -# Root filesystem for cross-building. -if(FLUTTER_TARGET_PLATFORM_SYSROOT) - set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) - set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -endif() - -# Define build configuration options. -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") -endif() - -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_14) - target_compile_options(${TARGET} PRIVATE -Wall -Werror) - target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") - target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") -endfunction() - -# Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# System-level dependencies. -find_package(PkgConfig REQUIRED) -pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) - -add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") - -# Define the application target. To change its name, change BINARY_NAME above, -# not the value here, or `flutter run` will no longer work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} - "main.cc" - "my_application.cc" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add dependency libraries. Add any application-specific dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter) -target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) - -# Run the Flutter tool portions of the build. This must not be removed. -add_dependencies(${BINARY_NAME} flutter_assemble) - -# Only the install-generated bundle's copy of the executable will launch -# correctly, since the resources must in the right relative locations. To avoid -# people trying to run the unbundled copy, put it in a subdirectory instead of -# the default top-level location. -set_target_properties(${BINARY_NAME} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" -) - - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# By default, "installing" just makes a relocatable bundle in the build -# directory. -set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -# Start with a clean build bundle directory every time. -install(CODE " - file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") - " COMPONENT Runtime) - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) - install(FILES "${bundled_library}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endforeach(bundled_library) - -# Copy the native assets provided by the build.dart from all packages. -set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") -install(DIRECTORY "${NATIVE_ASSETS_DIR}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") - install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() diff --git a/quill_native_bridge/quill_native_bridge/example/linux/flutter/CMakeLists.txt b/quill_native_bridge/quill_native_bridge/example/linux/flutter/CMakeLists.txt deleted file mode 100644 index d5bd01648..000000000 --- a/quill_native_bridge/quill_native_bridge/example/linux/flutter/CMakeLists.txt +++ /dev/null @@ -1,88 +0,0 @@ -# This file controls Flutter-level build steps. It should not be edited. -cmake_minimum_required(VERSION 3.10) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. - -# Serves the same purpose as list(TRANSFORM ... PREPEND ...), -# which isn't available in 3.10. -function(list_prepend LIST_NAME PREFIX) - set(NEW_LIST "") - foreach(element ${${LIST_NAME}}) - list(APPEND NEW_LIST "${PREFIX}${element}") - endforeach(element) - set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) -endfunction() - -# === Flutter Library === -# System-level dependencies. -find_package(PkgConfig REQUIRED) -pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) -pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) -pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) - -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "fl_basic_message_channel.h" - "fl_binary_codec.h" - "fl_binary_messenger.h" - "fl_dart_project.h" - "fl_engine.h" - "fl_json_message_codec.h" - "fl_json_method_codec.h" - "fl_message_codec.h" - "fl_method_call.h" - "fl_method_channel.h" - "fl_method_codec.h" - "fl_method_response.h" - "fl_plugin_registrar.h" - "fl_plugin_registry.h" - "fl_standard_message_codec.h" - "fl_standard_method_codec.h" - "fl_string_codec.h" - "fl_value.h" - "fl_view.h" - "flutter_linux.h" -) -list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") -target_link_libraries(flutter INTERFACE - PkgConfig::GTK - PkgConfig::GLIB - PkgConfig::GIO -) -add_dependencies(flutter flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CMAKE_CURRENT_BINARY_DIR}/_phony_ - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" - ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} -) diff --git a/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.cc b/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.cc deleted file mode 100644 index e71a16d23..000000000 --- a/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void fl_register_plugins(FlPluginRegistry* registry) { -} diff --git a/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.h b/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.h deleted file mode 100644 index e0f0a47bc..000000000 --- a/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include - -// Registers Flutter plugins. -void fl_register_plugins(FlPluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugins.cmake b/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugins.cmake deleted file mode 100644 index 2e1de87a7..000000000 --- a/quill_native_bridge/quill_native_bridge/example/linux/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/quill_native_bridge/quill_native_bridge/example/linux/main.cc b/quill_native_bridge/quill_native_bridge/example/linux/main.cc deleted file mode 100644 index e7c5c5437..000000000 --- a/quill_native_bridge/quill_native_bridge/example/linux/main.cc +++ /dev/null @@ -1,6 +0,0 @@ -#include "my_application.h" - -int main(int argc, char** argv) { - g_autoptr(MyApplication) app = my_application_new(); - return g_application_run(G_APPLICATION(app), argc, argv); -} diff --git a/quill_native_bridge/quill_native_bridge/example/linux/my_application.cc b/quill_native_bridge/quill_native_bridge/example/linux/my_application.cc deleted file mode 100644 index c0530d422..000000000 --- a/quill_native_bridge/quill_native_bridge/example/linux/my_application.cc +++ /dev/null @@ -1,124 +0,0 @@ -#include "my_application.h" - -#include -#ifdef GDK_WINDOWING_X11 -#include -#endif - -#include "flutter/generated_plugin_registrant.h" - -struct _MyApplication { - GtkApplication parent_instance; - char** dart_entrypoint_arguments; -}; - -G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) - -// Implements GApplication::activate. -static void my_application_activate(GApplication* application) { - MyApplication* self = MY_APPLICATION(application); - GtkWindow* window = - GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); - - // Use a header bar when running in GNOME as this is the common style used - // by applications and is the setup most users will be using (e.g. Ubuntu - // desktop). - // If running on X and not using GNOME then just use a traditional title bar - // in case the window manager does more exotic layout, e.g. tiling. - // If running on Wayland assume the header bar will work (may need changing - // if future cases occur). - gboolean use_header_bar = TRUE; -#ifdef GDK_WINDOWING_X11 - GdkScreen* screen = gtk_window_get_screen(window); - if (GDK_IS_X11_SCREEN(screen)) { - const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); - if (g_strcmp0(wm_name, "GNOME Shell") != 0) { - use_header_bar = FALSE; - } - } -#endif - if (use_header_bar) { - GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); - gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "example"); - gtk_header_bar_set_show_close_button(header_bar, TRUE); - gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); - } else { - gtk_window_set_title(window, "example"); - } - - gtk_window_set_default_size(window, 1280, 720); - gtk_widget_show(GTK_WIDGET(window)); - - g_autoptr(FlDartProject) project = fl_dart_project_new(); - fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); - - FlView* view = fl_view_new(project); - gtk_widget_show(GTK_WIDGET(view)); - gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); - - fl_register_plugins(FL_PLUGIN_REGISTRY(view)); - - gtk_widget_grab_focus(GTK_WIDGET(view)); -} - -// Implements GApplication::local_command_line. -static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { - MyApplication* self = MY_APPLICATION(application); - // Strip out the first argument as it is the binary name. - self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); - - g_autoptr(GError) error = nullptr; - if (!g_application_register(application, nullptr, &error)) { - g_warning("Failed to register: %s", error->message); - *exit_status = 1; - return TRUE; - } - - g_application_activate(application); - *exit_status = 0; - - return TRUE; -} - -// Implements GApplication::startup. -static void my_application_startup(GApplication* application) { - //MyApplication* self = MY_APPLICATION(object); - - // Perform any actions required at application startup. - - G_APPLICATION_CLASS(my_application_parent_class)->startup(application); -} - -// Implements GApplication::shutdown. -static void my_application_shutdown(GApplication* application) { - //MyApplication* self = MY_APPLICATION(object); - - // Perform any actions required at application shutdown. - - G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); -} - -// Implements GObject::dispose. -static void my_application_dispose(GObject* object) { - MyApplication* self = MY_APPLICATION(object); - g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); - G_OBJECT_CLASS(my_application_parent_class)->dispose(object); -} - -static void my_application_class_init(MyApplicationClass* klass) { - G_APPLICATION_CLASS(klass)->activate = my_application_activate; - G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; - G_APPLICATION_CLASS(klass)->startup = my_application_startup; - G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; - G_OBJECT_CLASS(klass)->dispose = my_application_dispose; -} - -static void my_application_init(MyApplication* self) {} - -MyApplication* my_application_new() { - return MY_APPLICATION(g_object_new(my_application_get_type(), - "application-id", APPLICATION_ID, - "flags", G_APPLICATION_NON_UNIQUE, - nullptr)); -} diff --git a/quill_native_bridge/quill_native_bridge/example/linux/my_application.h b/quill_native_bridge/quill_native_bridge/example/linux/my_application.h deleted file mode 100644 index 72271d5e4..000000000 --- a/quill_native_bridge/quill_native_bridge/example/linux/my_application.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef FLUTTER_MY_APPLICATION_H_ -#define FLUTTER_MY_APPLICATION_H_ - -#include - -G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, - GtkApplication) - -/** - * my_application_new: - * - * Creates a new Flutter-based application. - * - * Returns: a new #MyApplication. - */ -MyApplication* my_application_new(); - -#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/quill_native_bridge/quill_native_bridge/example/macos/.gitignore b/quill_native_bridge/quill_native_bridge/example/macos/.gitignore deleted file mode 100644 index 746adbb6b..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Flutter-related -**/Flutter/ephemeral/ -**/Pods/ - -# Xcode-related -**/dgph -**/xcuserdata/ diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Flutter/Flutter-Debug.xcconfig b/quill_native_bridge/quill_native_bridge/example/macos/Flutter/Flutter-Debug.xcconfig deleted file mode 100644 index 4b81f9b2d..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Flutter/Flutter-Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Flutter/Flutter-Release.xcconfig b/quill_native_bridge/quill_native_bridge/example/macos/Flutter/Flutter-Release.xcconfig deleted file mode 100644 index 5caa9d157..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Flutter/Flutter-Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift b/quill_native_bridge/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index 1acf04d3a..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Generated file. Do not edit. -// - -import FlutterMacOS -import Foundation - -import quill_native_bridge_macos - -func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin")) -} diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Podfile b/quill_native_bridge/quill_native_bridge/example/macos/Podfile deleted file mode 100644 index c795730db..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Podfile +++ /dev/null @@ -1,43 +0,0 @@ -platform :osx, '10.14' - -# 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', 'ephemeral', '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 Flutter-Generated.xcconfig, then run \"flutter pub get\"" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_macos_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_macos_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_macos_build_settings(target) - end -end diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Podfile.lock b/quill_native_bridge/quill_native_bridge/example/macos/Podfile.lock deleted file mode 100644 index 7d2149e80..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Podfile.lock +++ /dev/null @@ -1,22 +0,0 @@ -PODS: - - FlutterMacOS (1.0.0) - - quill_native_bridge_macos (0.0.1): - - FlutterMacOS - -DEPENDENCIES: - - FlutterMacOS (from `Flutter/ephemeral`) - - quill_native_bridge_macos (from `Flutter/ephemeral/.symlinks/plugins/quill_native_bridge_macos/macos`) - -EXTERNAL SOURCES: - FlutterMacOS: - :path: Flutter/ephemeral - quill_native_bridge_macos: - :path: Flutter/ephemeral/.symlinks/plugins/quill_native_bridge_macos/macos - -SPEC CHECKSUMS: - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - quill_native_bridge_macos: f90985c5269ac7ba84d933605b463d96e5f544fe - -PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 - -COCOAPODS: 1.15.2 diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/project.pbxproj b/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index d129dcaa6..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,801 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXAggregateTarget section */ - 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; - buildPhases = ( - 33CC111E2044C6BF0003C045 /* ShellScript */, - ); - dependencies = ( - ); - name = "Flutter Assemble"; - productName = FLX; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - 48F6F64E1D3B068E354AED5A /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDE555F7B1A8B2AA27DD31B7 /* Pods_RunnerTests.framework */; }; - 588077B9B7948518FF16E803 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 830F57774208EDAC364B3ADD /* Pods_Runner.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC10EC2044A3C60003C045; - remoteInfo = Runner; - }; - 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC111A2044C6BA0003C045; - remoteInfo = FLX; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 33CC110E2044A8840003C045 /* Bundle Framework */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Bundle Framework"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* quill_native_bridge_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = quill_native_bridge_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; - 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; - 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; - 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; - 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; - 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 7044F50D32DDE7ACC263CDD6 /* 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 = ""; }; - 768FD25A9A12B26278FEED07 /* 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; path = Release.xcconfig; sourceTree = ""; }; - 830F57774208EDAC364B3ADD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 83FAAE953977A0111144FF91 /* 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 = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - 9DF098AD994623996956FE45 /* 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 = ""; }; - B38D6AB7A0D79A6AA9DD018A /* 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 = ""; }; - CDE555F7B1A8B2AA27DD31B7 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F0079804D7BFAC0C2A27B325 /* 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 = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 331C80D2294CF70F00263BE5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 48F6F64E1D3B068E354AED5A /* Pods_RunnerTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10EA2044A3C60003C045 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 588077B9B7948518FF16E803 /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 331C80D6294CF71000263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C80D7294CF71000263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 33BA886A226E78AF003329D5 /* Configs */ = { - isa = PBXGroup; - children = ( - 33E5194F232828860026EE4D /* AppInfo.xcconfig */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, - ); - path = Configs; - sourceTree = ""; - }; - 33CC10E42044A3C60003C045 = { - isa = PBXGroup; - children = ( - 33FAB671232836740065AC1E /* Runner */, - 33CEB47122A05771004F2AC0 /* Flutter */, - 331C80D6294CF71000263BE5 /* RunnerTests */, - 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, - 758560ED93CFEE0910386647 /* Pods */, - ); - sourceTree = ""; - }; - 33CC10EE2044A3C60003C045 /* Products */ = { - isa = PBXGroup; - children = ( - 33CC10ED2044A3C60003C045 /* quill_native_bridge_example.app */, - 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 33CC11242044D66E0003C045 /* Resources */ = { - isa = PBXGroup; - children = ( - 33CC10F22044A3C60003C045 /* Assets.xcassets */, - 33CC10F42044A3C60003C045 /* MainMenu.xib */, - 33CC10F72044A3C60003C045 /* Info.plist */, - ); - name = Resources; - path = ..; - sourceTree = ""; - }; - 33CEB47122A05771004F2AC0 /* Flutter */ = { - isa = PBXGroup; - children = ( - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, - ); - path = Flutter; - sourceTree = ""; - }; - 33FAB671232836740065AC1E /* Runner */ = { - isa = PBXGroup; - children = ( - 33CC10F02044A3C60003C045 /* AppDelegate.swift */, - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, - 33E51913231747F40026EE4D /* DebugProfile.entitlements */, - 33E51914231749380026EE4D /* Release.entitlements */, - 33CC11242044D66E0003C045 /* Resources */, - 33BA886A226E78AF003329D5 /* Configs */, - ); - path = Runner; - sourceTree = ""; - }; - 758560ED93CFEE0910386647 /* Pods */ = { - isa = PBXGroup; - children = ( - 83FAAE953977A0111144FF91 /* Pods-Runner.debug.xcconfig */, - 768FD25A9A12B26278FEED07 /* Pods-Runner.release.xcconfig */, - 7044F50D32DDE7ACC263CDD6 /* Pods-Runner.profile.xcconfig */, - B38D6AB7A0D79A6AA9DD018A /* Pods-RunnerTests.debug.xcconfig */, - F0079804D7BFAC0C2A27B325 /* Pods-RunnerTests.release.xcconfig */, - 9DF098AD994623996956FE45 /* Pods-RunnerTests.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 830F57774208EDAC364B3ADD /* Pods_Runner.framework */, - CDE555F7B1A8B2AA27DD31B7 /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C80D4294CF70F00263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - EB8B52EC474BCDEAADAC6F27 /* [CP] Check Pods Manifest.lock */, - 331C80D1294CF70F00263BE5 /* Sources */, - 331C80D2294CF70F00263BE5 /* Frameworks */, - 331C80D3294CF70F00263BE5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 331C80DA294CF71000263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 33CC10EC2044A3C60003C045 /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - C9E331B712FA33539C773C66 /* [CP] Check Pods Manifest.lock */, - 33CC10E92044A3C60003C045 /* Sources */, - 33CC10EA2044A3C60003C045 /* Frameworks */, - 33CC10EB2044A3C60003C045 /* Resources */, - 33CC110E2044A8840003C045 /* Bundle Framework */, - 3399D490228B24CF009A79C7 /* ShellScript */, - AE8E2B94E6C70C9A86E2E5CD /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 33CC11202044C79F0003C045 /* PBXTargetDependency */, - ); - name = Runner; - productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* quill_native_bridge_example.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 33CC10E52044A3C60003C045 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C80D4294CF70F00263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 33CC10EC2044A3C60003C045; - }; - 33CC10EC2044A3C60003C045 = { - CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; - SystemCapabilities = { - com.apple.Sandbox = { - enabled = 1; - }; - }; - }; - 33CC111A2044C6BA0003C045 = { - CreatedOnToolsVersion = 9.2; - ProvisioningStyle = Manual; - }; - }; - }; - buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 33CC10E42044A3C60003C045; - productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 33CC10EC2044A3C60003C045 /* Runner */, - 331C80D4294CF70F00263BE5 /* RunnerTests */, - 33CC111A2044C6BA0003C045 /* Flutter Assemble */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C80D3294CF70F00263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10EB2044A3C60003C045 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3399D490228B24CF009A79C7 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; - }; - 33CC111E2044C6BF0003C045 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - Flutter/ephemeral/FlutterInputs.xcfilelist, - ); - inputPaths = ( - Flutter/ephemeral/tripwire, - ); - outputFileListPaths = ( - Flutter/ephemeral/FlutterOutputs.xcfilelist, - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; - }; - AE8E2B94E6C70C9A86E2E5CD /* [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; - }; - C9E331B712FA33539C773C66 /* [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; - }; - EB8B52EC474BCDEAADAC6F27 /* [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; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C80D1294CF70F00263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10E92044A3C60003C045 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC10EC2044A3C60003C045 /* Runner */; - targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; - }; - 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; - targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 33CC10F52044A3C60003C045 /* Base */, - ); - name = MainMenu.xib; - path = Runner; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 331C80DB294CF71000263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = B38D6AB7A0D79A6AA9DD018A /* Pods-RunnerTests.debug.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.flutterquill.quillNativeBridgeExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/quill_native_bridge_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/quill_native_bridge_example"; - }; - name = Debug; - }; - 331C80DC294CF71000263BE5 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = F0079804D7BFAC0C2A27B325 /* Pods-RunnerTests.release.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.flutterquill.quillNativeBridgeExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/quill_native_bridge_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/quill_native_bridge_example"; - }; - name = Release; - }; - 331C80DD294CF71000263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9DF098AD994623996956FE45 /* Pods-RunnerTests.profile.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.flutterquill.quillNativeBridgeExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/quill_native_bridge_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/quill_native_bridge_example"; - }; - name = Profile; - }; - 338D0CE9231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - 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_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - 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_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Profile; - }; - 338D0CEA231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Profile; - }; - 338D0CEB231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Profile; - }; - 33CC10F92044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - 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_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - 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_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 33CC10FA2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - 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_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - 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_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Release; - }; - 33CC10FC2044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 33CC10FD2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - 33CC111C2044C6BA0003C045 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 33CC111D2044C6BA0003C045 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C80DB294CF71000263BE5 /* Debug */, - 331C80DC294CF71000263BE5 /* Release */, - 331C80DD294CF71000263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10F92044A3C60003C045 /* Debug */, - 33CC10FA2044A3C60003C045 /* Release */, - 338D0CE9231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10FC2044A3C60003C045 /* Debug */, - 33CC10FD2044A3C60003C045 /* Release */, - 338D0CEA231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC111C2044C6BA0003C045 /* Debug */, - 33CC111D2044C6BA0003C045 /* Release */, - 338D0CEB231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 33CC10E52044A3C60003C045 /* Project object */; -} diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index bb3240123..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc14c..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/AppDelegate.swift b/quill_native_bridge/quill_native_bridge/example/macos/Runner/AppDelegate.swift deleted file mode 100644 index 8e02df288..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner/AppDelegate.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Cocoa -import FlutterMacOS - -@main -class AppDelegate: FlutterAppDelegate { - override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } -} diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index a2ec33f19..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" - }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png deleted file mode 100644 index 82b6f9d9a33e198f5747104729e1fcef999772a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z zKpxF`G*X(YOz2^@7i#D+s-~A1E;3&x%%qL5hkiy^JhYjJ74{hvVmAx*6BH`M`!qGC zO9pjEsR)A-n1`6KLACSL%FS_Kcm+?4*z-V?WAZPs?RkzoijIr~I+oh1^~T`q^dCFvG$Gbd8AnTYBjLKYUmayaQz#S1le7Q^Hyr#;X&h*1wDpm+gZC!rSKom zq|+o&UGpeXtlQ1;?@JukKG!8PGS1Io0z6O}ZeL&DsON^I0K+>Mxv#ohK+;ByAZ`Eb z2orY{j0Pa3edA(#-pJA0AaJ6h& z81Gl(pd#j~mrizktoid14K5ig7u8FvZmLLP%l@dl05IprCyqDB?mA2fc*6UB+49lb zZ8`V9epdo=OeZoiY%zw-w`8DNwTORV_>>3T{r)1-YsGSo0E2s>tix9OBqKFBjg#}G z`pgkCblKMYs!Z)r^(qT_c+}gLhR|gnq!1~Qr|~kt&2@_yswx{i$KEn`8J1W8BGljl zr@GEG#W(s#AKKyuqLp+cl1C}7%`m#-!$15XF{M(M*-fD%+i#mFbP35jlgN3{8#A-dmj&OQtG)!031jTwGMal=&YtPfq2AUWekP9J-JT(p099!L`+yen$ zVH1?kRrhV7(mGKkm_jPP_U@Xd;x=ppk}4WY0Rbr> z0MJM_;$GGxL*P68y%KBqHntF{>X&<{aeI4m6+{TQ%~Zp}v%Pujr)zg5mV;cFKqeA- zQm5`#Sd{B6Rc*4PS-rO(vf>YEdXmOK?>K@`L5}|9q}#t_IE%g+U<-1qw3mr5&v;2A zCQ}BEn9_u;;>n5N#dP0RhCF-_UplC+U(i~Zjh>U5+b8%@p3HK(R*IMQwE!uritb}< zF)AK2?+0@-aE3LYkg`B*&N&m~JWB9>(Z>`aqRwgioU)0w{U1K4?>-#i|ZfhNa9hV)2)(%ch zJMH1twoeZWwkE@I!dz$ma+;9GeACv>Ncupl@+gBSeU_uzfj!$+h&@EACkZG_vwLGA z(?^;rcJu1$5H~xI@6lHIYC-$+b&hF1p`AoAOKqw{t0Fu#X`OGt$)7Q!nmJ=&)xjq@ zHoxT4pcYKSPT5(4yzIuQ^S*N2NJpR4v0?rB-^JuaXNLis?E(l>Jo8mUw(gsFLLOy? zEszHWGaCn|lw$LSwoj{G7Uq(zK0W^VVWu#ms8BMRlF2z%-g`fOXmndgC(na8fc)s` zz$GAoxP+l|+T_S4$r1sLwkV77ew1Gug*`|HiE*?FGLm1q; z^p0A0eqqbmk3?|!CB9DBN1Zof6d7+ zJSn!`VD~tVaqy<*Mw^8dM5v3Bvj2VdVFb=)U3L2eDM3@>n(P z?Rr_=I17+r4fE{>1LBQG0&o97nef67n-aNnVP<{dd6*B!Q344 zZbsAof&jw+;CLeK2d87t9s~YZ5?6Qwf&{NPEBN+)LbjOcZRXNcR&h)x`TtdpI+b!>$E~h0o1L*2OddpR9!Gw~-E^Cj(7i69S<66ak$)AYMv|xG+;uR(`;h zGIV3}?+Qxdjz)s;s}jHY{JPmeo@-tN$H@hxaV@)}K?y~ts~E6H(F|SlsN5oH8g7*h zGiC!8c1doE3U|D}Vul1yPmXuCk*hmyU4MG2ml#V0+(G5I+`L_=3cD$%$I=@*8m-LU-!fn&-sZO1%ls63+w}AiAK`Jv z>`q~ztr&&(gCkFpci+*1Ekdv*MhBCzGfPBj9dM|YEjZk(tWBuz4?MGeq+*)t>Q=z6UXF_w z{QDUT4^JQ8J%hW;d2xGB>Fl4Y-bRT!ttP2GE5jYoI1e(eVK0&V5W+>zludt=nf|UN zi1IV;MK$Fy%$yw<oGeW?JIGjmfGLH$Y;l|T0p1V!N*Jvu zHSAG0WpwPip0vm7%VRq8$2O2>P5b!WBfTz*6dZ4Wd6O9Y(8A;nOuG((y?F`ac_u2( z#~17CoTK)1G<~~Z4jXlout{e&nZbDHyHf(=a?OtaJ(2Q(!g#)Ugw-QQ?A?mN#yN%T zBtJ`sA6Lpg`k>Pi8a7GssiY$eG0Be8LCoQL{GDqi-;j0pLmT!Z)szldvbN7GVcu*S zzb1rEq|M)1qa7rM*I8!<#w7FnQ?{v^? z0`MlS3+`#ZB5$DT4+`7e-Hlp_2G0`*F@STbRJ|!tk3cC~1T%NR-p4s=sTT+RqsMjF zyrp-Jv?CD4Y3N&Zb1gr=%`MFR8;|r)uxQ6*X{OpEhQ~+tu}^n8Wijiy`pSMw0uKNi zSNX^Z1y;WirM0o_x%zft0U2GcLm_2BS`b{Z>g|9VOVr%QF*R?pTpiJsEbj4jLVAyd zTA;x15=f~b0^(e*Vo;Tn;WTJSxpI9LmL($Lxob<^S!k7mGhnnVNnAC*g!$ms0#Q|q zs=25I0<>fUw_&+KU`}5P9wlmjRWdMYh%Np6n?AAHQ;JzG?s(Z9UR`pNh79Nzk~DF+ zX~jy>>f-2bl?drlM8 z3NfIQnrT@pLmv+QA6efWPv!sqe;mh3_RcOj5>Ya;4hhN13dtx*_TJ-=kX_kZQDkPz zIw}#e_dK%au@1*L&iUP^cfH?zf1iK)tHv=t|>-9mMT!;;Vg|svSzWkN7q#t$c4N$Q;tl3EYwef_4q>GO<#I89VhY;`X*hz$n*GZ%f+;uViG z?uLlxD1OIeid}0r9%Ssoc7@vJjZIsZlU9zvYpjhYiOrzD5sq3OC zpf-X;Nb!DLpxqX^zDIK%=46-Z3%i-bac`RIBS5*wcw5Pu>G|kF>TQP$dGRYh#1hwD z{|cbbTOKL>Gb1-;X6?vWLC+KJ_^Ij?KzJ7eZ?^8XNgoYU9^z&>d zsIjX*uOK`#Wu!`>L@y!=XpQcW+mBaRjm|XrB@etLdr}Ob57e7EkE;7a*t7=M#XFL6 za;KHHk-rBNTjp-gS^;ehKNv>K>+_jPQ45J%4><1HyKJ?;T9#~k_23?xD}B&@Wp{%H z($hU+nWR?g!9dsJkgVz(J_Yrdns+m~9V_gQ7Sb`&F4wZZ!k}##j$>O{4{?avCbCZfyW zO$)m7LE=P?$CXHDU_RUD+sYwT;nKI7 zSs_XTv!BuxpJ!7(b~uYfsgzt~mj5(vf2r~`LHwpePs!o2A3zEr@#sxo8HEe8>V||d zBiz0@e&6}p*}!6jsm}I0bN9Mc2(c#jg@;Nu6!Kv&4&P8-UcQ-00WJIO%4OuUn;^jU z;I3r=T3KQtiMQ7&x32eVtB`mCe)9ws^7u%2P`B%Xc}=Qc&O^{FmS^{~Rho}^s`B+H z=1_T);9LRK?{$Vx22!5m)Er8aoPOA8&{7fyt`t@~Vw%gtx~+g3qs8LFR%(2Uny28A6dFYnNQgcUa>Sq=%alFh&8#@1o_qgwve* zVFimnUtL{4aHP6s?FB%bu2SP=e*VGqXC8iuZ-JOc{5%Lx0g|VvyWkdh&FD^Gkc!0N zhoolXvp6GC8wj?Y+V;r*EN+<1ac`-+!8Mqb@Nz)=OqV?4gxhR^t7*+^+AfxxVt(n{ z+fkk|-xSGqmkZa@Q%`;;r`-Z|? z0fR6b@l%pTwK*@xY+(MwBUwf^z+F*~piC64BWTrz}-HS1-XF-IA%?Zs_#F8 zcmUuEZ6Of>YIJOe$&{V;3vIBw7|jSGPeS6cvTMdj96Y~pI-z7InGW;(DhFqaiTTO9@KWvQi9__j0btLZ9 zAa~-Po%^sDFfme4@Yiq}r`BgnYK2eTwCjg9_zC4V{{&_GTm-!qHGVR6JXDjw;}GzF z6lXA{xo1+tQM{9vwb1&sRXPdGDHbEMbnwh}t+%tvcw5p4J4r#hEpDl=A{;Mjc%0)T zsG}v<$^HhdcE)5IJ^iBWK{7?Zn)vb%c!5eIj4 zbT}CGO*u)Od@^LuIC@_2{=AP2-O99NglFudj{!T}0e8wtTQcB@F9QW6$J!0Ye`T+U zXDx84b$!hD#4YzSyZLy~!IIZuFa3%eU zG4eg5?}sZ6Yj29P^-PcXG*8%VzLL$0!oL?c(!oQ+G!kORsa+lsf5YER>PX83R4LgF zgPNQJ#Bo#)MXU%J9k?RWD;c>|as5b5p>xAwau=X5XbERX`_ZHB8_XSNDe`s?n(e>) zGF$G%n6o+W{6A-@4hsIK0*J%jpB#Y*G^B48eQD(CDZR5oBl-P=)r7fH^PLf?!aK6V zwkIM35?l*I6p@;^H}JIDNs-fF*IFN?k?kj(M)QKM%%?dSkf1d$Nly2z(>)oq8z}0H zH?Qa{x&36#W@y04!9zx@x7un@ob$&)V8#f~0n1|jF0kFs4aZ{ND1~QjWHToIY5)LY zrgKDCj@dFCx&-w$QMi=CqD*=`$NqC~2k366pPXl#>Y7A=iQD}f`)+B-pS@LIW_M?9 zlBS_)(vGz!L$#P`?<3Hvonw@B1uJ244y)M?0)z0-hq++sJ0GZ+{oiiH;lFi&wy(C! z0Bv9z^M;`4@)USP)7dhg@K5K&U&|7&-@I0Sk>I+ZH75_xEn>qh9qmc%aA@NEKBsVBgUuK zC=b{w-0oU|)~tAVI zyJ3BAB}%rsjz7qZ?x_XCWe6!_u-{e_3u68Asso0IvwKdxq1lN#%4w>J zi>}P;$JZ>58(ZAjsmSJl6BWUTe`0eGEf3f_yS#H6vx;UJWO7CCK!{)4C}`C$j5gNj|k znb$4QRurEE3tPEe!JzG-a0DmvXePO zSD#Q-qOAjTMm|=aBSnvwHoEbgyVIz@J$hT*legak-hhb}e#%cm2$nR2 zV9A{kc)WT$np=5coPQIskbGMO@Fn2NxPv$@SJZdG6}jV;+%(cH+*RFQ(+DjsJlman zy`D(yN?8MCtjWD3w}Q|jQccb$}BDW%M$zZZnri2+5ls)@@(wQD`jt_GpTKL_^CO&SSCcHbfMX#JXYFI^*947 zPh&S-G=l*C@`E5CU1$m7ao(Q&oSmY7)ZZ#5_fEyYzLsFJwJ%GfErFeRN@7lUbUrL| z$6;gQSNsI91LJvT+$Zb0>g<4g8T{B!U05lfKmoSRH^pB^^8sJ3{8PzVq0NeypMF5k zU3qOqksdq{>AUjm3O~dZx^vS6C$ldgCWszl?xd8-sJ;-kPnISB*-f=L*8XggOx$?u zg%B-QovSjBbj}%sShZv~r?`*6PiiQW;nee<-=+y4}S#}q_BgXIJoSOf$YbE7vXt4;Np zrKzZf6Ny0aES8(-cqmnIGMg&ieYWryBZ0VTB=4<*@auP4NdIk&q(Mt(OLPm|Yl za!0OpC9sA#tk>OsaCSx0;!$5r6naw ztzLBo>#LKaxxsO=yWe%yGilL`A|6E#TK! z+1VRQlo*D?(k0-mlRM+`OMT8kVB*-%ZGv}Aj1u^j!wu*~>L<-T+u?6sX!3C}lQte- zk(6_=iwXsQ0JbRvJDwMnk!c99w~s~uD_4vMB=m~-ft-*|z~$*g4g;pgG~Ap1m@@Fx zWS)8IKSN6`^vVQ8hv^Oc+O(Rt7!U%wVsGP+Y6fyS%GG+v+dIdVfCXPzAV~~li+3m5 ztFQmbE)(#2#Oi@k$1#zUS6ijD_yYsa{+BHZAw+^zAEI3bc(h0qm?|pNf?oS}Km#OG zrOfCKn_-CVO;}DXu|5YE#d8I2o>}vUxYlv&>=+I28WY>a1;uI)HUM_IvpF;Ln4ROT zf!=1rpKihNFUo=R@sD-pT!EOm%%ncl43f;aem^;|A#s3`b6vjeAzO!M-gwc`-Kj~{ zBX)tq64*kJl#TrgW4o%hTY3x$P01nD6a6s2#MmwM$vyX5PU|YngU*wXGK*?f?#Eg$~^OWW3I@of-=XVuu-b%A1Z|nqY_2 z;~jD&=QnB#WGU>;RwFq(I< z34K1fCMwf9F}G%k(&?~2EY&)W*-_z0ReS$;7+I1)zz`)M zpAF{5ZHLPMJhYU z;GE*@hM1NM{G{L94dL$!Y-h6A9K9W=I6AYb`Y=v{(tpyLQz^^Aibea(q()R*TU|-m zozpyr!|-BZ_Dn+$*2|vq2Y@ghHo!-`WjVtU-bab(SJp2*2i-}$UP9^qnF_OIFS~-< zYj^VS!)Wu}vn6!LDIt!HJ1SU-@ce>z8f4cT4R9V@O^Xg9)4`VpjsXm*~@%l^Ux;Rf#Zck`BNXu0Y(!C zj%Z}UAmD00nsOS%Uull)dU(fZgJ$bo>3Oa`8h~Wt)EM?v(ndlTS1p0|E9Pg>=&>58 zghD~%R;YpqZAw;F;M(lx5b_wkVbnd+ER+6A-SYj^1XUgNGn0I~ES|f|5emjyPIW)S z0z8i6)BZt&h(qQxih4HbFYa6~jyeKbc_`QEdLD@9SBGButjw|b^l*oQjDk<7Nig08IK zb`ATVGzK%LP+>9aFM0hr8t+m`uNr?h&8o3Rp$T&ql||K}7GgobFhCViaDH~+F#yC- zt>7T3&_PZ*feTKTyd6vlF~JmEA1f+*>CCE4ex}5N^$4o)YuxX&3T$P0(IS!+kan^J z_p>v#1J8bWELml|S02YAQe-&yVew+kipZr~H-I@yc$=8#rZ-8L<_nDx&Qv3dJDwUX z!)@=h1`~R2M{$J8bM^1O&Gy2oxe1T;K?NA{iv_eYuhpLyc3%xu%z`dVc}Z}%cHGHQ<7P!Q|e?dwnSpL!AUf!B^!?#^Q#W!Ry+7ofwPZ1mZq z(Id0{htmX1W?2cAYWZo_lOtT#+Us-nlP$=CGK|Ri4x0Xh>(|iN9y1 z=9y26A4Y}ViRi9Fxzm{>J`YM>GX1D|$4BY9xJrY{oY2~Z&};B{Zq9Pp!pox`8e#0C z-h~@fohA74(#ws!{7kIe4v6XUX<)9bd)g66Bz%^Y4p0~OF+rY;l$v&7T<3~4y!bv> zR$r#LblZcVgy2lq!ff+>yuR4qCcljQa03x|dTcG7`CHcxh#POtGKt6ymNd_0qF7Wf zBj_KC8{jl!zZ>0neDp19n3sD?HC=|WM3!}cK4zCnu6Uoj*hbV1<#F2BD)@A~y%@VXx+u}Hcn=_s-({PxzmMZ^xJ1SV zoZMY*FarYvO_@z8Lr2ep)%HgIL7rhYa~#X&&V8oYSw zA4m{3{hw1Vb~~26K^xro&e7i9eg^SqK0i}kG3z(!_~E?sjJlSWIWXJqKiHAWTG*SpPcCMD`kEc1gx`R^YkYWz zEN4vEIkj@&e4tC!(_~x`-K$w6CU%X7U2Y z)Y}T5stEyoSsB{H{+xfST3tov~6@lO}2gx#N(rHXiOAHT!dp6FiV8V)B4{L_P_% zmX0rPa^-{1xG6|#uEGo+!v)QAOjRe|jg2ICcXU!|Cr+LMbLHlhJ)ErR*P9*z$NLlt zmYjAUbljq004ZyOco?HJovV7M*Wb2nF8vT2D;3kGi%F)6Kr#TVW>}zTHnUQxoGmD0CY9J`|d%8@}n;_co2q zWr98`R_c@PQbMi}x3bWo4XZj{it6qYj+o*XvNoS4>rF;7WNn;vA*|A!3H}Wh-uk@n z*hV0S+XnX;K;BOoz?&*9_{NnM25s4^^QUt|>R!()^Z6#G3OmL{CU^-IG_M7_a~B+& zCrV;ouC1ljbK(K=ygqAE_-}ewnH2&&t0enS7}I4i0wJgNvCf|P$`|DHku`K`HfDa2=n@DCg8MRi_)vpMR2Mxy4PE2Qe! zD||kNXy=0WeU(43v%md9Hg9Zu#CP%d%C67gk_#pfXs8lf>M=betm(}0fdDKq0{26# z_c?J!Cgo-~*=wswLXkR|W8d+rDdV00`22Ouv=_Hod9bmB!=D$I4r@7DZX7e+0tO!9 zR{0d}A6^K#yRx@ykotO4(WUJsmFvN)d-o-wZ(wcDSUS`8jO-JSAMa4y@MK4fDP`(P zzxQ2})ofiauWKj9{Rm$Yw^?g=?`oO(Vf|T^I+-A+o1#F`>tn59d=FtgVJAV=y;G&` z0GMvtEeil5;e$Ln8-41(UeMl2kYLk%vPl?0+Egg_;g)494o5FsvdeZKP;&&fjw7o{ z|B+e%Z|)8Ts?=>@p|hr!nYXgV=ZjI4Cp#$E>+g^6r7Nd3<>-t=G%B5IyZUI{e{49G zqnIXEB=M@5Ndf1J#l5YWcLG=A4ufF8S{z5Kz-uM?Ni{{%mr);=l0=473h#cIc{K3> zZ-VUw_Ng5^HgWQhs5tQU@qv-YBej9`R$a^|lknX<*+sSVXue8M0#EPBJ6_Liwl*8l z_zoD#!l%WIXJZ$jm?|zUu0LdeP&8IW*(|39&QzKGnem$6--u{ZGtHt#Hro*h)?lu zXGKo-4Hv1WP*VLj;uA6UwGSV*6ro%PRbwR{@tXoCOb=OFTB4ru-|Id!rP5Y6LF*-D zy|t0qDSVPo$ffyoj#CIZV?l3VsPRYye$F^xxv~Z78_fwlCWbwW!nYCR2nx0_+@tg3C_UDMVa2Br=X3hfP}^Cp4Yg=#OK}K zKYVY`V9jEKD!UrCbSX6Xym2T-cg}!n;?;o{mM|zWj0P@D|FO-rQ zKt#ApEh#AX%_f%9!G6`I*K=bSnMIhQ%W5&BOMntzVr*eS;WR;FgM)+k`#+Vze*z&V zkU^I-R|!Nwy<~>eeQ~hJqa2|DdpX15kD=6U73Du;T|VarycBP^n#IZeIJ&H3S9#@oec~poZELqX$DAc>XZyuIqd^GK0Jq~0kI=d zA7gMo8%zmkEdnqMh)tkp?V0I;Tm3`>aU3^~dXw zlhdd3=iygnUgYu#GRhxln}4D?Gokczq?T;RjCk0=fUHy18$lt!-q!%sNxee7No^+N$9d?Es*``)0UJ4SC&FNY0pf z_MlbGdUy$|F}YDvJ9GTCkZbsNKj3DL5;=BGBx8xI;n)=A0d0j6MP7Mi6MQdk@Tux2Qy`oI_&*%EQ0bE?|R>P$rDhcFa8O?JIK zPOpFDa?-L*+Q7RrCg#y5z$l0d>n@+OYo3g>-Z*x&`Jj5|=*UOYaJer6;FAbdtt0O? zrFGUE?!XeUG}G8wMgeTs%+r;3uUU;Nq5EuU{h-g&UOBKhdS`;J=m!~xn*ztv_p@dD zR)tR!P=~5kX)FRsx9)uyuu?0dh%Ht7`PTM@e#Cq!z2ts;O;L)tQ1ipDiWqbGz@o_p z^D=UKR#`S7HAt4vQtD(_SeWyj_av~#tJKlb9>-s5Ykuzx_E1ZNl4)~f=zG$*;-y=T z2ozmFva9az<{2&63fQ?(Q8{IPx@t1LuFcxP-LXVctWh3AwazVTt2)w^*Zn-#eB`bD zSHoAusjOBK5(>uQPGj=ijdOH3jqG?(<5#C{*JQ?Lt~@zow=Ii4Al$Vr!#+Cf-gx)A z`_h(>b@7?*6bYM8%628gGW^rwWoG$mK_eCk`}B&llStfwHf12*{5spmTeNH$4{gCY z@Yuwr*k@%m;T<60bw9z6^WpWi@Bu^qe-g;YAzI+VjgsuZaGA=^G*I{KLy@rIjSpWb zFQNsCp2T;S$VaJtZ<(waRu8y7^X;>YhsWp zM)mKgCeE@K;J4vQSV z&-(Gl5AJCp>K*2-`U|4i;u3p8xo6(isu-38>cY zml1Eo&FBBKJpour?}q&nggpFiGM%m+YX`ng8P+uRnJiMyWcv*_AZ8KAB$w;rfmN8C z<-2EB6TqZO>A~P{*<);wYqZgxQS8E*syOXvGkGxF@s(scud0uv?T)fQ z(DGrwM7lvpitUG~6!*}kZUpBn9PuP`5^nMK@($xI^0Q~axP5qU>L~uF{R_<9&m z({}$$WuD1y-QzMVb3jLPk`~bDJNkw(Dv-6cKUb4uzD= z-w?i0NZ2K}AbT}Zi^uOZ32xmSxJw+6(3j%a!~Tdy-@RxVx6YUw2|V6JX+mSJNclfl zF~SD#eo+lnB=ZpHLl{)E+`sI^-V1Vn!6#Ml_W4aH*Pe(++sNI`M=5L3?X1z0;CJeE zJiX5Mp6JH*=R9W0t(1@>>1y=lP^F=yJil6JxU~I}EpTsBx?rJ5LbCbQ zuLBmmX1MO&!E}khx=+#hCesIB53`IWwqyFtR{AUv7vJ{Q^dn1S0@*^UOmRwctFy&> zd={(J@avBzmu$MbyamRMt_$kfHY<*v)%%&nY4hUDH=$k)$8LHlUG0G3Kv#T~-vQjw z)hXbsNIg?~b-jRw)ir5Q(gfwM+Zk+0haf z+4ER%>T8RnKAoJ-(s&tu&-iZ@A?^J|d z6md=9C4am*v2r=aa&a?~37bc($n#wQ<8UGXL+!RtrRXGSj-2INJ#+3J=}e6nOC}G8 zN~lvCS@rxoq7w$CLg-wx!%V%ymw>~xhUw4cADX*$A}D~{21F$!Y61aHwpdL!QcrsN zl~$s5kk%7HWHkZ43%mOcwlk3RcbKGQ*}K(Fxput)rpE0zH0vY(EyY=blQZ`odG#hD z)~{&r6XkSE(^csqsaMm>2c%xsT2&g_Nab1bTY%fIoNHatDY@C@Ei~v@19|F?szU6SWRS)uDXqNY!48RlAb;S*ijqus; zp;bteR835>3BXML2CewOM<^q3M*ubU`}gnI-oS&(vf=GF|JJB-inGOH_dc1xb|iqR zWgrcNy?1*8)vAlAaiBE%K3Q>5Ygy-#Wf$>FqL|Kvgb&6H?iQC*Z|PN)xZJhH#d#=a z@s9O0oea6Lg}submzNZ{iZ*_okZ$6G*h5YO!dE=7c4=YA9g$y%1xjkVl#|1DShEjM zH3(sS?uRfB3mhW5Wrm} zrY>KpBxM&CC;s5Ie_{o}upN{vdb8x<_$5iiQN49`z`+Zz`&E`yLAim;X&}$HAfKmT zkO2Dgdno95mWMH~h2c4);H=MigT8hyzl|4g;dU7F;p^X>w!fa0zf{^rf?>~ z0w{=F_R}ru{g5i@&xwC%R-!-1x|(k6pSb5_)$f`zyErIvSCs{z`iVvU4x_znFKti!!av6BkRX_=+kEc;*`_rla zB`g4ruCJGT3XVTTrlh3Yj>1>PNIy?sV%Yo*=qaBIOY87_?P04yx6TV?_{~K? zOHEo3|2EA2JAMPYZM!H<{|!s-$r>l5{19icxV`Wf-{<0I>{v&H4FZaCy$B6Ludz{v zRH!!HV#JGP?5(L!Zp#}NlOODgWqjO+yo~+LasPYxH+ht2KjdfCFQr(oovP3?vkFK^5FvPJ4^LD=DpYQi4tUXuY1;erJaBQ79 zHcp(>mKvoD+)bq5SX9siR>(%CL??*D>Snn%p}NfGO4(RY^puLI+j$Pw)NZLb5bKo{s|0L~ z-A3R~;QHMg0bHSgESOM&N&@oF4|8gkPF-nVM=sQ;d}wcS{{!iW-)yQ``D6t#xlh(O zRF0Z@O>0uMz9g)u{P))ptV5lH2(gC8I5i(FDRG5Gp1bgBydKgxJy5gBfK(#D7NzZU zatG}S^z#KL*Do5=K*F7hk(`mbdgI1XoM!8*-};#UzNtEG@Nki#`7)GfV;VlfW^)=` zBaAjK5>gx@wf_D!B!2C6xBK^K4%x|+#?P@5N7tlfWo6xWJD~Wz^cnPfFF($Ixt4!j z9%x^1$on56XZB0Irm^kw-*rd1YVO;(*LbB21@7OPJspo%WO676#~oUMws(zP#+shG+$ns0IC3W z_{kYU>N5<_6=j>*0d}r-?8U+--eXfy2M+opoYL|=I932TMp=&k#tzJ^72OtRJ8BVOvTYPh;@EE=LJLeOk`y?d|Dd9%fWlhON^LnB^6x0LyZqz@imyogJ`$C@Lr9Z4o)ZQz>NCavG$$@e2#r3 z4I=}I5KgV>wl)~_Ja7gLQGju0c1{h%cV&6c`doWWv$>q*=ZLc8J{hBiKXNK?zx2Nr zz!pph;BLU2OaZTv>Pzj(VpSp2&OWNCF<~>NgL!nezhxEgj;&2 zl>z@V#>sykFCnFL?|(j)J3SFr|FFa`n@KbhC2pZB7 z#3>qIn&~mG_Vki=p8_x&CFeD4V7MvgJlk^G7H;(apFxr+7Gc0+1KfI6$@aeF+d7DJ~_-A|H=0?Da#&^Cqb=!=fVz>giW5nw=jWQBS%L^t1EZ@ zCm9;qlG{($@0W3T&l17ownc5pWhfM8Mwn-fLtb7H|IYl)8@QikEc_Le+s60x?&B*m z5kObB5{BD}gGr7l84~vP{N)C~3V;xhBWd%=^j0&KBw3T3-HU`;hqWA3OWW~<8nl-M zfYn-BI0_?g`3$_;&Exw<(G{QM|8)Kq28x9NF-F$>r@_BO)t^T*i-U1bX01<)zC_uE zR@8qEQQ#cm$YbXIUPVO?z7KI$pw@r=-V{V@>dC9Hn==1QBVy_b;#*jR+&f*$AwCl?o&G?2Uk4=*Ej zFK^Yvw*HTO9n!XRBWe++o3)4O!OC9PC=_l_<$M(W8(Akk`zv5?nJifb^rH3N?Hhio zo$=nNmSEz_QFHj|XF!vQEcdqPyZz_4|M_GBH)k)KA9XGRlTJD;3*y1c#?ZWkeaQM* z^`Bf04#Z)ARgrE4rMmlk8E5F=NpaW8xKNd3)-orW$m+kh(W12jQbQ7oi z)=#qbmhkplt}u`FC0sV9sdnb5$E!zX_xlA{4wW&j0*DCm`=1;Sh_sB1xiH@C89Z93;8d)EUk=lPNIZ`o3H`Vd+Ig`=CV}#?PAXvzWk{x96fn z0(rYh<>?PJ>Hd8v@c8=*vm+)>P1k@i2>yMaKw2nihLV6Z;wcdc*E2{8=xNh(FkEe3 zq_pc;ISw&}`?lqKx<4vIa67!xu|P}G$c3MDyg?u^InS?uM6Zzys0QM9ChW>g-ypzA zkOUSfvhTTWq{_>TJ{+kpgwX{@>P5ptiJ1NTO5)8 z8BiLUY_!*AJ$V386^TicK@z0qOPWP#Ea5?}!$_&fQ zOcRKuR^tLX*&CM(ahYftiNg!a=uU|He)2nU2(~iX@Yo|foZp906;o=d%aK09YEW7_ z-yX*;XE#z@?zZ&fQ?2fYX!T8@-$(K5Jo+AkyOM+(944x4B%2NR&avFFJY^9_br5UtzSX5@gmYYm@ z@S$jtqFn18bXQr0IYhQ=+2~ZDB_DRW3d=*B+3q`-*1P$i!GVIG(AMp=vBQ#^_mNxp z(;4Iz#_~&9jZ}}7oW?R;_x8&h?b0N326NJq4~>W^TeI^!o4=G5G{|9ff|`NN5+?ns zL@IWva(*@PXPmVGQ#rgIOY*nnoqNDDy$hd2uMT>wBgzg>YT&BV2U{k1ah1(1j_v0` z@o;6~SUGW=!+j!oa9ko_2^G75?VolPmWk=Pb-h{k=phZga( z88Rp7QzbHkpYG!aug9e^DF63Bi|1#CeAW^CpakO9DTT!p$yhuT8Aq10^cl2O@Zl-2RXr`+zCPj#_FqXs}W2{Qvn2Y{BmNsG45? zB{BF_rVgT$u0 zE8o6|@C>uOK1Ba}!V zx!M$9J1B7#_JSs90cKlucib?T&HqQpLE9YV1?v{gh2NWKEt9FX8;3DePnCL5Z=k)Flp=?-i$<5H4zc z`?2ZZ+p~Y8FYr;m3Vn2(u5Z`Av6#S}zkpQpZ|vNP0DY^I-oa$HXzg+ajQC7%wldRN zfOAL!UwFtuphqqR41v|3He4cQF5;UU9M~lti-k<HSTs^#>-Tf|C2&~#m%6WZAy1jz!Q_-IbpZP z8ht8}UG13lz+N-7+01+RlE)6OT^3px7fn@1|_b7^{bhPet}< z_)77(<^>8-qQ2X(n4faVhm@T0@Z{5HFSWs~EDXtV@7IAMbVUP6;v8^%l3PZ#wOZ-* z*Vk4lRj6OYpAZ_$*`t|tYKmLar&&{5{d+5cst)rQTn`n8>Xi+0zXc6YbTPMgzewFg z23F=+`8=FXXF6b*CDVN$v3|6iy;TSFSYh$qrbhKDcT^U9l zj}3g#zty{k*>s8S+>t|cng#3@Rz`z}njy{*?90mV6_Mkvv=iL9pb0ttHf$7;TxkX1 z-klTGb`2~-Mxx6~+{b-KiFd3XG`p?+6-0PMorB#Q@TY_CH5)En#5WrmHqj;@Fvi1A zeGpO@wuYIPOgRY&02e-U+j7!$LZ#5mS72R3MJS^gfheL5`kQV_n{8}KXaj)V%4b~As zFrQ7yZal}~{ELX@8c#V?2LlM@)g(|;VvcBjEuTJ=`WkOem{DL!+7Lr!U;F!mGm_^~ z+V^T?%bz+8noq9{ybcq16Gzd^fS2`skac)@6|;8X8l6Q19epZ@l^3@1ES!x2XLNA4 z_FI8#x5sq7hXVr83D;_5$sU!*Ye}zyx1wMC?Q{DSgrUx#fM?_Fj@{syA2x2yL^J{S zPPLkQ#O+9E9a^H*USdriL6rGHDt$B!vu~t7^)@_e=(<|SVd!MenX48AP(Z$4WoC9_ zeN;I;hEAr{ZvB^gK*1AWfI~5H0a{Y#2UBjn9`7;3JDrI5leeufemoZol*pDlVTSHP z3#8@6kxsJwUFg9(;)>Xm!{nsFC<7}Xwv_?o=eP)$>vvvj>yw z=YS7{pIOg(u@mJ%G0G^TM@L6>l)?_{_e`(yLxmX%h*D zMJS13@e!}HFR{?GNtq;%=4#zUgfFP^$g|Ax1<`vC&qIPbwGNo}3>ZM?=Evk6r|J&S zi$UD-za)A$kcqu)8)1mG z{FI*zS4{wM6S3;RP-!$0&8!6*;>|%T%HJxZt}cmap#~4vD0Pkx22gBbPo~=2iEMFa zSN<~qRz>jf54?e)>3%j;Gc6C1_YO0C|CDQDt7+bE({$0($tizZ)xn2L?@6_ zR3$`yiwH?E%X*^k*^oQ=z!1GA|E&fXHPR=rIEGq4%0=SGvror2Y%k#d`aPmx5@~7a zdkmPa1d-<`6M%& zp9rn|?C(5SRowEcasXoE$)s`=GvJk9wPt|2VX31T2F}6x3#(&IMqZND*a1muBh9?X zX_HSLo?$y$a;qFx^U1W|YAd%)Gaf|AEHqZ*{PW96FF*&nO-@c?c6t5=K_z@2f$8<^ zY}d|9NRviy7sF$61>@bV$B3*VeDg4DX3qScxVTL~5Go^T?}aG+th- z2`EduJx~ZcSssR;yX%oW&ze|$TF?;>HGHp~Eq?$w&SAD?d#s$$|4F@l*T7}X$7>}7 zRvPwxrPaLO5X-qYiQ7{P^4Ui2GDbq&DJ3Yu`)8zfMi1{>HEq`+uR1bJ4x!#n0D6_M8Zs_# z3mc%u30aK|avL-!XI&?{^%v4OXUr4OzaL*|-HV&M5GPx)SUqYMWw@Ex;%DHx^&FOD zncjYHD@AiYbGx1O(rsKW>Eg}cid)6bqA}!r!G{?x#)c?^k+q_uv%Xh3ha^A^{%wnpRPY({1LqK{NQy>!UjUc8f7x2` zgyLiGpsKlFO75ee2#drn3Glyna)PvUP}e(t6P z(8^W6g23+fzT5gZQQ^L-Yg#^P;QK8FTZAe)*|CKS6(I>8a2aoN+XEkYf2jAF!Zi3! zjS($tF@bu(ypeC>`IZtF;jz`F6A-Y7ZUQBuZxp&q4zHb9cc*!1`T3p9xL9`nWhNVr z!2lf=fCA>;1E&E|yfmrHqB#XnUCu28b*4#eZ{lLL(42#`ui?BO&uZj|d_Fh!Bw8g$ zn@2uezsJz@^XM(T{!CEw+EyG*eaF`FuTN%C zOZg)khBpDobCl(3ud$bhr>EdmuQ^l^Cic|y2m>LM+gsZGYKUAeJE5YUX9}j^JDoojv<}Cm&t+agmp?JE0%d#fo}m_cYogpjn5&egilTvDFz-Df}1i zB4)bXfn$dqb!cCa13DdCgMNehaa&${n5Mw&bxeKfNmHq%e{T_H@WB!H3QgFK2gNpB zP<;xkez-y-Lr(0^P^G!YH~WLut`0=mPXbVN64iv6Nd`s=eUQ;?V((+QU0&B4SF3*{Pm$AVrq;v&)c>VLy_UCe45VEsI@ZWM2TaB# zRU6XaLx0^H=0)Z!$rIu`3*s{Z!W7pU@6aHvX*vUuzME+!B5H}k_gFD)3=f;nI zi1|B!@iO%p;L{!JSEI~vyUByf_{HY=;RuAK##-h!06XFwxYi?xl}oWStJ*P{OcVe~ z_v(y8!+BaLQB`(D(XrL0ReKMn$R)8mU2@$q$Pq; zbZq-$IkP4V(`m}e<)cwnZLrjiA-X0@VY~Gi5-PKX20#Eag!JOw1br%7Rr}`(v@d!u zCo@&wE1SwM=zt~$K!eJ**9GAv!}Cogn9(d0X~BwPkU4gaWh?WVRcE3N?C%_R_D)Vw z(YmJTJ_0~fhItqHPqoIFGQYE2!~?aSRa{vjcDWhy5>oT zGOMFTWfL`aLx-!QL(9r?~D6y9Uhq=af8z!rqg#p zXk%gE-;=@G>MUv7p@P#ni@zP*$YQwA0Dlc21`%pV;p!_F@xI(^eA5&SZ{rU?^Wj}! z6Y%C^eMYilc_~MAwqV`h=I0;WA)MqJ^$IvyJ-O0)*RuLYjTL1TWd|(NbhIZ;nOop( z`4bc=fsxaeI@zc!vvYFFetFRKSMjef2_#oIzzPIxZ4oB0sxKOzX4Wltz#G@LD2Qr5 zm9o~xF;EU*_!O`}IigC{sU%1^$$B@>Fa_H0*>*1Amc^7tnKxcPpr8zZTme`6(0@J| zXfBE;0)lcuv%tqq05V8P2B^)Nhq~qdR|1KCfe>(GeuFaNc)T~zvma>o)FZv;sVD@D zynx%jpd8m<{zI zz44BQcmN85TNhy2plu`Nt$b;sKELSBpW)my@*ZnL{lFaD|7-8c-;zw*wh@(1yH+~o zQd6mwOU~P(B4CS|mX=v+F44&NRvMbQpcpDmU!|BhndzGgrsa}~;RGs*v>~aLX|A9$ zxrCyC3y6ZiciVh3@BH@t1LJY%FM8{e94DY4JQ} zYS0fcOC|N!{@iq*a@H$Qe9ONriBWJrhLhC?o5K2)!=~i)0hGh-mMd~RkqdIGCB(fU zy5*IvHssJ&gxudt>g(3w2{)axskJ_#h96qTc~<{c!`n^f zg+SOfdm8=UI!4%}d%RkXd}yWU1H66h)eDTsQr!qkcZE^zbI#F$k(dn7l7z}@YSv1+ zIcEYw{HJjfg()x7R@zQ&o;LdJ2vi6Fkl?OHM-Ga!%w}co(6=I5LZ>n{9pr~6!z|S$ zq_VfE7##n|{H(t$wPI-D`~L#((@V(MZ>p6Eb8k%4{lIGT;hZ9cg%~HhcbDCd%0RbM zs?uZG1wSL{Z0f+NzDiO?w9~XT^dWptKJ@M~0(@5*az*ZgabU465JN9eFY7vD8Wdz_ zlAIonnlivB;uDXov3sIgoKx2>G6a;@?v0qg;r`RnZ{4wMw2%}(e*c8k`R7sNT@>H} zfUU~mHR~8!4rJTHVlT=v3wz2kx&95Nz?@Tj8)s5E}t{|AFA=d_Y zOTqb{ATx>U``k~NJ2hYk3r#Gn1}|1Xj}jq!9%;{k(?9!WZt1z#{OATvapC-}#$LWi zi2R>~v0v6A<|?Eg)Ye#VyRyr7RJ$N4vFEFfmb1jHF(yZN^rc!ULDen>KWu(D9Z5!P ze(qg(G2HmSqyi2B&W`vo@N=3l?+dXbWn-`1LrY1^_mSilpKLLxQp}@s?=Tqw6Do5Pui*IhPZtaT|GAE&MF$;(4s9Bt5f+vbITElRv3( ze&@3GgY%ltiz;PZXq||TeA+sP9bc(#*G<2ck&zF3W?0$Bxit`EwvZb7jke;810>h3 zb}}!oS_xUbJ^$_PWrSlJ-;v4qq!@|L9uM#ALcMu|+|fni+AqPpu+CtjBrs#Y1jKVU zEc6L$d!2l-MgMi5&7?{Dfxj)qn;mIZudn7I6V$88%05A!PtCQTGSxXKMGh;qXa|fE zJBUmhM!}@e#A?s%bajm+=Ka1WxHZWaj;k#XT{T#;bH9c5zA8txVHEz(EeE*PP9eD9 z<2|evdxmVLj_n@`lp>6@ zy_ZTczm54_lGjPwPaq$dF1HdIks&Mp;%bge$QZnnp${}#&Z3)z95ei@b9;c=kJpY- z$G#RZbgyTi3&d4=3%+gXOSp|g^~^%K1id>re4gTka;7m@WA}bFo`GUbT8-n19VVdO}IkuW(H_iil_S}@$xy(Q*fCcNaD60 zxqsWK5lESLWnKgy^ci@da#k9^aW5)oLzbFxlUVBA&UM~79PF7=rW@Ot`>9(Gju3N{A4%EK0dPuz{=J_LUv|Pe^*x3eq_ExMNjB3?{$+xH^_Y z;e5pH)*~Lo@y=;b=P$Iqp9KR|j(>D-kaI4WeI&&HPFRtbZBMiQ^PwE`pF$Z7#(@UF zP2~&InXDTNx3`4)H2mD8yHl{Jk(|C(VA2vwY}3IRqo*qy9HvN7a!$$hlZqjmb6tZy zp1fLd^be5LmcI`_d3@@A`jLDS!b0qXVvP%y>+DfL86Ie=*TZ)PL??Lk^F};4=dwv; zPRBV>*)f&NE0vtjYHw@vs9l(Dk*g-}ARSciwv!f)E361d_9y<;9b7)PBw$3dh`AZi zAY4)BVh3t>;gR=s)nZW3PT_3bOLDK)eTZT^*m%P!HdC!FvK=Z=_iA>Bg!`SsC|P3u zz+oMr^PUcTebccFK>bqp475+?5RUC{Y7klp^p=Q;ZM+c8Zq6wBtH*5c=QHlp7wZS%6AszeebN>>_2^H7uuK@g%1{vF}DT>U{h`}c+u5ubXcFMH)fZ6-l z!y=qVN>jqgj)3T!mALcM;1!8}PDcMCU6<9?l#euNff${zE=b0d%;TcPFfw`y>zjLg#_WgnwatH|t}Y&WrR32m5W_AWNa`OqIc{ zW{_mX(Ck1psRCgMhJ*hXhcAG1ocb_kuY)%9rlYzq8h$K;X}=5m+8CYpJ4Yw6zLi%S zpu}dkAc_hVv>NfWy9eLsQ-6OzoBl{WAkRi|U;anmJ5dFwz(C9~-A(!Vfw z(E!S5ua;@}(q5GrIc6|PAOSPg{il$s$UBI}tk5xuP-VedGyZd}xqXvWvU_`{;Cf0> z5fN79T(#iq-q$RLb(of0ZA0lfepj^!a2-6 zv{v^7r2J*xmj&XVgZ>Wd=RqwGGe1`-Svll~bz(-y7*N1ooU5J*aY@&5ea5ss6n(a? z`N9l?w~=^1g2wLDVRD5ovqLc^Z#YRDFR+QYV4emH*fzOpzer3>Pudh??f``be>dD3 z)xB}1O6bZpnt=j(m92Fxq0dz89n>B05xx10QDL-YDz&e>h_u@9+RG)Pv4{2IYNiMy z8auH}j+fW*;q%Ymtbq+KI_r4gxGUeYJ>hq~vbe!N3%NntH+Dyh7I70!cu(qE_`Vp; z07NvH4Q2s#9;mKj;>umoviK|H+#CbgGq`D+QxI*$r6&D`yf%-M^{H;6gi4*j3?c9c z8$}NK?0I4%b?c`p2;SvL3*xY`0fe_KIZqPm`M%{DCrPUt{bS|zlhbHBNlUe7zcK}E z$L2zIl+z#Z!thJW!}{G&JAC@Pg`H(}GLM_m;uV}C9Yt(vF+F0Dy7{`k zY&v=ZZf?8^qSD>~2iP#{qQK632aMplZye6Q3X>dctS@JHSz2)zJaqXvFEZlr>9$oY z^&9^4pN`1EJcEw_wi@P{zJqQX470?WZTB*5Y7F!3#xJO^z|Gw@)bFoY5#daTP5OgI zcbKI$Ok(|9g_%#If*$3ga=U0_n%|#}eWwyeW~(19Te+!xF*(rd=LU(nM15;<7Z&oA zrqIw#r7}&_qgCdvS7+!|3?8w7JNRtHQ$~8Yyw(xC+n=- z7SQBo3+)tbg2NJn^=lukNOCkiEsgt~4tCrZ{aSnrHRMk@_?1^whFrEn3mT1NSC9B&c-(JrWu@FUhSNf+(>-_%kX#@LYnzq`^M#XX}(*!_LZCY za24(5Y$WH^=;GY^#0c{Y4{_!GPvm_bd#&6ypUpfwu%|+=UEe^Q+oe$7cXnyF@O67L3%SKO#rdayD^4^vH2hG{w%vp|_*jKf4 z=jb?40UP4S+Mi~(Uz(^cvgVB+r+Rt|;wnFRYcz(i=&Q14Ok=V-tTPw4%v&;ZrxI#w z6&rvLjj#yzBr5~N*7o09CkIE=>EWwo`ceL*@Y=504RB*xY#SY{)p3Gvn9zBL_FCN0 zl^axu8p~su8HpiDNi{%5ojAv1{0?t7*mflF9&Y_x4#)X(jyLl~c+s6*I1G7{zBI;tH*_ z94)o##4$cU4ohj~e#C^E><)3E`d;ftdwTQZpDmp)9)n5^+h%BE?)8LI2A`L!zjTBL zPYE&+#0&jDFc&4Tg}VC}E@4ZGyWbiK2dvn6Mpu!cQT_^6!RG!7)fE>V>?PNFm?vc5 z>A8gcW=5Xm2#LEW_;XgMQ$=Y-#lc|zs2}}2ny_4Kb%D@Vrtu6rOmUe!ph7;;L`XHi zXcDHc;OYbIk44?|A9-=Ml{Xap)^{jb5$Kl?v`CIT`bDXV*x{h+UARtzOd}#US>a%X zOdU`5^_P@lkQxB*B<&RQB?FgJOH2-~rMnXf_{5%~s&OlUM^i30FeOM{`XOXs)3_BU zEAyNr%bz8RJ=Cvw8y=)3p z`K|i!j$l~LqQ)kabHK}7WeyB$x*({t#cQWf98qh&X{R*Y--9)~g)?XCL>&z;v9#hY zTFY?DV&1fPE&*z}6Ki`Y5#(-eVYB;OzZjPSDnN%ArA8D>wODpQT4Jt}ah556JE+G_! z_P0uQ!qDhR94VdpAqajIOl4~>oTaQ8H5yXaTZUOb%cRAkWYV?KSNlTqgSM=Wgf)JP zz=?Q5f5zPEVO!NbOCbqEwP^Ff_O_`gdm67#U{Mp^_bKcq2IoO%zcJb(M5z`cjv1Ck z+!awNRhwjj6CQqu+xC#{UWo^3+h?6ymzq3r?3JV}<|u_9x=MWAm`1AqAnOsJ*@)^4 zr|`FkZlg{Cd!#Chmhn=_ZQe;~-DTUOv>)Tbmh0{z_42vWa|vNUO% z_5KA1xNHBgw0zjUH|s5xg$b4k z@Koa#-AFizrr6h2#$k*41tm7_jp$yL4X*DZcklq!u+>9E0WnhcOFPn7Vh^ao@~tno z@RwY)*+8&|Hpdq)`a=L*Teuw;_B@u;o!a!YaOO@bs-?*gqpm?nRkXl~mKFfF z+OVzE%RlC`M5-+KM_GXZ@9b;=2C(sq+R&Ko_RzZ%5P~kDieK3yzV4BN*{$E%KY;4k z)s?*vacHYN~u+?SoI`e@S2!9Co!cdvz;@N@{yj`0-9^8osR(V7PR-O&gM)x3owqs5oJpIwc zgY`#VzjI$V>YYDrIr8D;0JK<10@ycefw z;;oV(!gUR*xBg%xTl-#d>u(5}#jFrLKo}q0b{IuuZhuO7n++ zo@9)d#`(AT$mbW5g;c;&z>1_2Nk%;L?TIhfeK%PYp>5N<5wdihxw4-qvVsN6t@bol zDFgi~t`B&ZU3ek!#fXVE5Ao$7AwI+@amT_m2SclwQE{cLcv3kwhokq+!S%>Fe_*(Z z75)vhq@YqZqa~Hf$0S?T@nr_%mV%*aT${~4)6|(P@Bq_Q!VC4tZa`7?ra`4?oV+wSr2`TVSUmKS_>V@3%0*S#!+L=3f@oF=4k9U9xv0p1;Fx&}V;X2J~h zcz^}G3|;s8JyEFR*LB*fPUm+?f+ofnBQ5uK%NrwA+RV_~h<6-mw_wU?NGRI!zNTh% z&>ty6x8&gW75gdW)?p->&%?{*brS|k@b|(>&<^nyO55Pi_q*eK)=J*Uunw2cw--p%E!VXuDa? ztZ$HPKJ6$Sh7!UrpxVBLFSnpZOw$(ftvg!Nk1LVfL+FL(u zh1Abu(oCSmgqQ2IrE;Zz2f2DAD%T4XO6tU&)2IB}vV3{^xpz1MYFEPy_09RP2QvmA zIqw<(UaCnCs!mFX$+3sjnV*(O5)y`jW!*wzF-l^K`Bxgap+0Ej z@c^nf{Ic`6I5#9bcE7fwiiP8JZ9dr3FsD~SBiW_`8{UgFt*{$@qj#E)90JYra>Zs3 z$sCTuzOye2GdTO;4@;wgJK@!ij-|c--insluCR}{#q=D6Xz#nL6;`rkc*UzLTR%Y{ zN2YK;Zcz4YY=+|(0_?E=#~3U@I1fIyRiBF zIeWj=id+b|L;kSMs>NMfeB^(={IdrC;NYJy_$L+olL`OdOqgH0OpSa?FTRhwb<|%A Pe7HEdAEg|=c=LY&YVNkY diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png deleted file mode 100644 index 13b35eba55c6dabc3aac36f33d859266c18fa0d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5680 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 diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png deleted file mode 100644 index 0a3f5fa40fb3d1e0710331a48de5d256da3f275d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 520 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K#jR^;j87-Auq zoUlN^K{r-Q+XN;zI ze|?*NFmgt#V#GwrSWaz^2G&@SBmck6ZcIFMww~vE<1E?M2#KUn1CzsB6D2+0SuRV@ zV2kK5HvIGB{HX-hQzs0*AB%5$9RJ@a;)Ahq#p$GSP91^&hi#6sg*;a~dt}4AclK>h z_3MoPRQ{i;==;*1S-mY<(JFzhAxMI&<61&m$J0NDHdJ3tYx~j0%M-uN6Zl8~_0DOkGXc0001@sz3l12C6Xg{AT~( zm6w64BA|AX`Ve)YY-glyudNN>MAfkXz-T7`_`fEolM;0T0BA)(02-OaW z0*cW7Z~ec94o8&g0D$N>b!COu{=m}^%oXZ4?T8ZyPZuGGBPBA7pbQMoV5HYhiT?%! zcae~`(QAN4&}-=#2f5fkn!SWGWmSeCISBcS=1-U|MEoKq=k?_x3apK>9((R zuu$9X?^8?@(a{qMS%J8SJPq))v}Q-ZyDm6Gbie0m92=`YlwnQPQP1kGSm(N2UJ3P6 z^{p-u)SSCTW~c1rw;cM)-uL2{->wCn2{#%;AtCQ!m%AakVs1K#v@(*-6QavyY&v&*wO_rCJXJuq$c$7ZjsW+pJo-$L^@!7X04CvaOpPyfw|FKvu;e(&Iw>Tbg zL}#8e^?X%TReXTt>gsBByt0kSU20oQx*~P=4`&tcZ7N6t-6LiK{LxX*p6}9c<0Pu^ zLx1w_P4P2V>bX=`F%v$#{sUDdF|;rbI{p#ZW`00Bgh(eB(nOIhy8W9T>3aQ=k8Z9% zB+TusFABF~J?N~fAd}1Rme=@4+1=M{^P`~se7}e3;mY0!%#MJf!XSrUC{0uZqMAd7%q zQY#$A>q}noIB4g54Ue)x>ofVm3DKBbUmS4Z-bm7KdKsUixva)1*&z5rgAG2gxG+_x zqT-KNY4g7eM!?>==;uD9Y4iI(Hu$pl8!LrK_Zb}5nv(XKW{9R144E!cFf36p{i|8pRL~p`_^iNo z{mf7y`#hejw#^#7oKPlN_Td{psNpNnM?{7{R-ICBtYxk>?3}OTH_8WkfaTLw)ZRTfxjW+0>gMe zpKg~`Bc$Y>^VX;ks^J0oKhB#6Ukt{oQhN+o2FKGZx}~j`cQB%vVsMFnm~R_1Y&Ml? zwFfb~d|dW~UktY@?zkau>Owe zRroi(<)c4Ux&wJfY=3I=vg)uh;sL(IYY9r$WK1$F;jYqq1>xT{LCkIMb3t2jN8d`9 z=4(v-z7vHucc_fjkpS}mGC{ND+J-hc_0Ix4kT^~{-2n|;Jmn|Xf9wGudDk7bi*?^+ z7fku8z*mbkGm&xf&lmu#=b5mp{X(AwtLTf!N`7FmOmX=4xwbD=fEo8CaB1d1=$|)+ z+Dlf^GzGOdlqTO8EwO?8;r+b;gkaF^$;+#~2_YYVH!hD6r;PaWdm#V=BJ1gH9ZK_9 zrAiIC-)z)hRq6i5+$JVmR!m4P>3yJ%lH)O&wtCyum3A*})*fHODD2nq!1@M>t@Za+ zH6{(Vf>_7!I-APmpsGLYpl7jww@s5hHOj5LCQXh)YAp+y{gG(0UMm(Ur z3o3n36oFwCkn+H*GZ-c6$Y!5r3z*@z0`NrB2C^q#LkOuooUM8Oek2KBk}o1PU8&2L z4iNkb5CqJWs58aR394iCU^ImDqV;q_Pp?pl=RB2372(Io^GA^+oKguO1(x$0<7w3z z)j{vnqEB679Rz4i4t;8|&Zg77UrklxY9@GDq(ZphH6=sW`;@uIt5B?7Oi?A0-BL}(#1&R;>2aFdq+E{jsvpNHjLx2t{@g1}c~DQcPNmVmy| zNMO@ewD^+T!|!DCOf}s9dLJU}(KZy@Jc&2Nq3^;vHTs}Hgcp`cw&gd7#N}nAFe3cM1TF%vKbKSffd&~FG9y$gLyr{#to)nxz5cCASEzQ}gz8O)phtHuKOW6p z@EQF(R>j%~P63Wfosrz8p(F=D|Mff~chUGn(<=CQbSiZ{t!e zeDU-pPsLgtc#d`3PYr$i*AaT!zF#23htIG&?QfcUk+@k$LZI}v+js|yuGmE!PvAV3 ztzh90rK-0L6P}s?1QH`Ot@ilbgMBzWIs zIs6K<_NL$O4lwR%zH4oJ+}JJp-bL6~%k&p)NGDMNZX7)0kni&%^sH|T?A)`z z=adV?!qnWx^B$|LD3BaA(G=ePL1+}8iu^SnnD;VE1@VLHMVdSN9$d)R(Wk{JEOp(P zm3LtAL$b^*JsQ0W&eLaoYag~=fRRdI>#FaELCO7L>zXe6w*nxN$Iy*Q*ftHUX0+N- zU>{D_;RRVPbQ?U+$^%{lhOMKyE5>$?U1aEPist+r)b47_LehJGTu>TcgZe&J{ z{q&D{^Ps~z7|zj~rpoh2I_{gAYNoCIJmio3B}$!5vTF*h$Q*vFj~qbo%bJCCRy509 zHTdDh_HYH8Zb9`}D5;;J9fkWOQi%Y$B1!b9+ESj+B@dtAztlY2O3NE<6HFiqOF&p_ zW-K`KiY@RPSY-p9Q99}Hcd05DT79_pfb{BV7r~?9pWh=;mcKBLTen%THFPo2NN~Nf zriOtFnqx}rtO|A6k!r6 zf-z?y-UD{dT0kT9FJ`-oWuPHbo+3wBS(}?2ql(+e@VTExmfnB*liCb zmeI+v5*+W_L;&kQN^ChW{jE0Mw#0Tfs}`9bk3&7UjxP^Ke(%eJu2{VnW?tu7Iqecm zB5|=-QdzK$=h50~{X3*w4%o1FS_u(dG2s&427$lJ?6bkLet}yYXCy)u_Io1&g^c#( z-$yYmSpxz{>BL;~c+~sxJIe1$7eZI_9t`eB^Pr0)5CuA}w;;7#RvPq|H6!byRzIJG ziQ7a4y_vhj(AL`8PhIm9edCv|%TX#f50lt8+&V+D4<}IA@S@#f4xId80oH$!_!q?@ zFRGGg2mTv&@76P7aTI{)Hu%>3QS_d)pQ%g8BYi58K~m-Ov^7r8BhX7YC1D3vwz&N8{?H*_U7DI?CI)+et?q|eGu>42NJ?K4SY zD?kc>h@%4IqNYuQ8m10+8xr2HYg2qFNdJl=Tmp&ybF>1>pqVfa%SsV*BY$d6<@iJA ziyvKnZ(~F9xQNokBgMci#pnZ}Igh0@S~cYcU_2Jfuf|d3tuH?ZSSYBfM(Y3-JBsC|S9c;# zyIMkPxgrq};0T09pjj#X?W^TFCMf1-9P{)g88;NDI+S4DXe>7d3Mb~i-h&S|Jy{J< zq3736$bH?@{!amD!1Ys-X)9V=#Z={fzsjVYMX5BG6%}tkzwC#1nQLj1y1f#}8**4Y zAvDZHw8)N)8~oWC88CgzbwOrL9HFbk4}h85^ptuu7A+uc#$f^9`EWv1Vr{5+@~@Uv z#B<;-nt;)!k|fRIg;2DZ(A2M2aC65kOIov|?Mhi1Sl7YOU4c$T(DoRQIGY`ycfkn% zViHzL;E*A{`&L?GP06Foa38+QNGA zw3+Wqs(@q+H{XLJbwZzE(omw%9~LPZfYB|NF5%j%E5kr_xE0u;i?IOIchn~VjeDZ) zAqsqhP0vu2&Tbz3IgJvMpKbThC-@=nk)!|?MIPP>MggZg{cUcKsP8|N#cG5 zUXMXxcXBF9`p>09IR?x$Ry3;q@x*%}G#lnB1}r#!WL88I@uvm}X98cZ8KO&cqT1p> z+gT=IxPsq%n4GWgh-Bk8E4!~`r@t>DaQKsjDqYc&h$p~TCh8_Mck5UB84u6Jl@kUZCU9BA-S!*bf>ZotFX9?a_^y%)yH~rsAz0M5#^Di80_tgoKw(egN z`)#(MqAI&A84J#Z<|4`Co8`iY+Cv&iboMJ^f9ROUK0Lm$;-T*c;TCTED_0|qfhlcS zv;BD*$Zko#nWPL}2K8T-?4}p{u)4xon!v_(yVW8VMpxg4Kh^J6WM{IlD{s?%XRT8P|yCU`R&6gwB~ zg}{At!iWCzOH37!ytcPeC`(({ovP7M5Y@bYYMZ}P2Z3=Y_hT)4DRk}wfeIo%q*M9UvXYJq!-@Ly79m5aLD{hf@BzQB>FdQ4mw z6$@vzSKF^Gnzc9vbccii)==~9H#KW<6)Uy1wb~auBn6s`ct!ZEos`WK8e2%<00b%# zY9Nvnmj@V^K(a_38dw-S*;G-(i(ETuIwyirs?$FFW@|66a38k+a%GLmucL%Wc8qk3 z?h_4!?4Y-xt)ry)>J`SuY**fuq2>u+)VZ+_1Egzctb*xJ6+7q`K$^f~r|!i?(07CD zH!)C_uerf-AHNa?6Y61D_MjGu*|wcO+ZMOo4q2bWpvjEWK9yASk%)QhwZS%N2_F4& z16D18>e%Q1mZb`R;vW{+IUoKE`y3(7p zplg5cBB)dtf^SdLd4n60oWie|(ZjgZa6L*VKq02Aij+?Qfr#1z#fwh92aV-HGd^_w zsucG24j8b|pk>BO7k8dS86>f-jBP^Sa}SF{YNn=^NU9mLOdKcAstv&GV>r zLxKHPkFxpvE8^r@MSF6UA}cG`#yFL8;kA7ccH9D=BGBtW2;H>C`FjnF^P}(G{wU;G z!LXLCbPfsGeLCQ{Ep$^~)@?v`q(uI`CxBY44osPcq@(rR-633!qa zsyb>?v%@X+e|Mg`+kRL*(;X>^BNZz{_kw5+K;w?#pReiw7eU8_Z^hhJ&fj80XQkuU z39?-z)6Fy$I`bEiMheS(iB6uLmiMd1i)cbK*9iPpl+h4x9ch7x- z1h4H;W_G?|)i`z??KNJVwgfuAM=7&Apd3vm#AT8uzQZ!NII}}@!j)eIfn53h{NmN7 zAKG6SnKP%^k&R~m5#@_4B@V?hYyHkm>0SQ@PPiw*@Tp@UhP-?w@jW?nxXuCipMW=L zH*5l*d@+jXm0tIMP_ec6Jcy6$w(gKK@xBX8@%oPaSyG;13qkFb*LuVx3{AgIyy&n3 z@R2_DcEn|75_?-v5_o~%xEt~ONB>M~tpL!nOVBLPN&e5bn5>+7o0?Nm|EGJ5 zmUbF{u|Qn?cu5}n4@9}g(G1JxtzkKv(tqwm_?1`?YSVA2IS4WI+*(2D*wh&6MIEhw z+B+2U<&E&|YA=3>?^i6)@n1&&;WGHF-pqi_sN&^C9xoxME5UgorQ_hh1__zzR#zVC zOQt4q6>ME^iPJ37*(kg4^=EFqyKH@6HEHXy79oLj{vFqZGY?sVjk!BX^h$SFJlJnv z5uw~2jLpA)|0=tp>qG*tuLru?-u`khGG2)o{+iDx&nC}eWj3^zx|T`xn5SuR;Aw8U z`p&>dJw`F17@J8YAuW4=;leBE%qagVTG5SZdh&d)(#ZhowZ|cvWvGMMrfVsbg>_~! z19fRz8CSJdrD|Rl)w!uznBF&2-dg{>y4l+6(L(vzbLA0Bk&`=;oQQ>(M8G=3kto_) zP8HD*n4?MySO2YrG6fwSrVmnesW+D&fxjfEmp=tPd?RKLZJcH&K(-S+x)2~QZ$c(> zru?MND7_HPZJVF%wX(49H)+~!7*!I8w72v&{b={#l9yz+S_aVPc_So%iF8>$XD1q1 zFtucO=rBj0Ctmi0{njN8l@}!LX}@dwl>3yMxZ;7 z0Ff2oh8L)YuaAGOuZ5`-p%Z4H@H$;_XRJQ|&(MhO78E|nyFa158gAxG^SP(vGi^+< zChY}o(_=ci3Wta#|K6MVljNe0T$%Q5ylx-v`R)r8;3+VUpp-)7T`-Y&{Zk z*)1*2MW+_eOJtF5tCMDV`}jg-R(_IzeE9|MBKl;a7&(pCLz}5<Zf+)T7bgNUQ_!gZtMlw=8doE}#W+`Xp~1DlE=d5SPT?ymu!r4z%&#A-@x^=QfvDkfx5-jz+h zoZ1OK)2|}_+UI)i9%8sJ9X<7AA?g&_Wd7g#rttHZE;J*7!e5B^zdb%jBj&dUDg4&B zMMYrJ$Z%t!5z6=pMGuO-VF~2dwjoXY+kvR>`N7UYfIBMZGP|C7*O=tU z2Tg_xi#Q3S=1|=WRfZD;HT<1D?GMR%5kI^KWwGrC@P2@R>mDT^3qsmbBiJc21kip~ zZp<7;^w{R;JqZ)C4z-^wL=&dBYj9WJBh&rd^A^n@07qM$c+kGv^f+~mU5_*|eePF| z3wDo-qaoRjmIw<2DjMTG4$HP{z54_te_{W^gu8$r=q0JgowzgQPct2JNtWPUsjF8R zvit&V8$(;7a_m%%9TqPkCXYUp&k*MRcwr*24>hR! z$4c#E=PVE=P4MLTUBM z7#*RDe0}=B)(3cvNpOmWa*eH#2HR?NVqXdJ=hq);MGD07JIQQ7Y0#iD!$C+mk7x&B zMwkS@H%>|fmSu#+ zI!}Sb(%o29Vkp_Th>&&!k7O>Ba#Om~B_J{pT7BHHd8(Ede(l`7O#`_}19hr_?~JP9 z`q(`<)y>%)x;O7)#-wfCP{?llFMoH!)ZomgsOYFvZ1DxrlYhkWRw#E-#Qf*z@Y-EQ z1~?_=c@M4DO@8AzZ2hKvw8CgitzI9yFd&N1-{|vP#4IqYb*#S0e3hrjsEGlnc4xwk z4o!0rxpUt8j&`mJ8?+P8G{m^jbk)bo_UPM+ifW*y-A*et`#_Ja_3nYyRa9fAG1Xr5 z>#AM_@PY|*u)DGRWJihZvgEh#{*joJN28uN7;i5{kJ*Gb-TERfN{ERe_~$Es~NJCpdKLRvdj4658uYYx{ng7I<6j~w@p%F<7a(Ssib|j z51;=Py(Nu*#hnLx@w&8X%=jrADn3TW>kplnb zYbFIWWVQXN7%Cwn6KnR)kYePEBmvM45I)UJb$)ninpdYg3a5N6pm_7Q+9>!_^xy?k za8@tJ@OOs-pRAAfT>Nc2x=>sZUs2!9Dwa%TTmDggH4fq(x^MW>mcRyJINlAqK$YQCMgR8`>6=Sg$ zFnJZsA8xUBXIN3i70Q%8px@yQPMgVP=>xcPI38jNJK<=6hC={a07+n@R|$bnhB)X$ z(Zc%tadp70vBTnW{OUIjTMe38F}JIH$#A}PB&RosPyFZMD}q}5W%$rh>5#U;m`z2K zc(&WRxx7DQLM-+--^w*EWAIS%bi>h587qkwu|H=hma3T^bGD&Z!`u(RKLeNZ&pI=q$|HOcji(0P1QC!YkAp*u z3%S$kumxR}jU<@6`;*-9=5-&LYRA<~uFrwO3U0k*4|xUTp4ZY7;Zbjx|uw&BWU$zK(w55pWa~#=f$c zNDW0O68N!xCy>G}(CX=;8hJLxAKn@Aj(dbZxO8a$+L$jK8$N-h@4$i8)WqD_%Snh4 zR?{O%k}>lr>w$b$g=VP8mckcCrjnp>uQl5F_6dPM8FWRqs}h`DpfCv20uZhyY~tr8 zkAYW4#yM;*je)n=EAb(q@5BWD8b1_--m$Q-3wbh1hM{8ihq7UUQfg@)l06}y+#=$( z$x>oVYJ47zAC^>HLRE-!HitjUixP6!R98WU+h>zct7g4eD;Mj#FL*a!VW!v-@b(Jv zj@@xM5noCp5%Vk3vY{tyI#oyDV7<$`KG`tktVyC&0DqxA#>V;-3oH%NW|Q&=UQ&zU zXNIT67J4D%5R1k#bW0F}TD`hlW7b)-=-%X4;UxQ*u4bK$mTAp%y&-(?{sXF%e_VH6 zTkt(X)SSN|;8q@8XX6qfR;*$r#HbIrvOj*-5ND8RCrcw4u8D$LXm5zlj@E5<3S0R# z??=E$p{tOk96$SloZ~ARe5`J=dB|Nj?u|zy2r(-*(q^@YwZiTF@QzQyPx_l=IDKa) zqD@0?IHJqSqZ_5`)81?4^~`yiGh6>7?|dKa8!e|}5@&qV!Iu9<@G?E}Vx9EzomB3t zEbMEm$TKGwkHDpirp;FZD#6P5qIlQJ8}rf;lHoz#h4TFFPYmS3+8(13_Mx2`?^=8S z|0)0&dQLJTU6{b%*yrpQe#OKKCrL8}YKw+<#|m`SkgeoN69TzIBQOl_Yg)W*w?NW) z*WxhEp$zQBBazJSE6ygu@O^!@Fr46j=|K`Mmb~xbggw7<)BuC@cT@Bwb^k?o-A zKX^9AyqR?zBtW5UA#siILztgOp?r4qgC`9jYJG_fxlsVSugGprremg-W(K0{O!Nw-DN%=FYCyfYA3&p*K>+|Q}s4rx#CQK zNj^U;sLM#q8}#|PeC$p&jAjqMu(lkp-_50Y&n=qF9`a3`Pr9f;b`-~YZ+Bb0r~c+V z*JJ&|^T{}IHkwjNAaM^V*IQ;rk^hnnA@~?YL}7~^St}XfHf6OMMCd9!vhk#gRA*{L zp?&63axj|Si%^NW05#87zpU_>QpFNb+I00v@cHwvdBn+Un)n2Egdt~LcWOeBW4Okm zD$-e~RD+W|UB;KQ;a7GOU&%p*efGu2$@wR74+&iP8|6#_fmnh^WcJLs)rtz{46);F z4v0OL{ZP9550>2%FE(;SbM*#sqMl*UXOb>ch`fJ|(*bOZ9=EB1+V4fkQ)hjsm3-u^Pk-4ji_uDDHdD>84tER!MvbH`*tG zzvbhBR@}Yd`azQGavooV=<WbvWLlO#x`hyO34mKcxrGv=`{ssnP=0Be5#1B;Co9 zh{TR>tjW2Ny$ZxJpYeg57#0`GP#jxDCU0!H15nL@@G*HLQcRdcsUO3sO9xvtmUcc{F*>FQZcZ5bgwaS^k-j5mmt zI7Z{Xnoml|A(&_{imAjK!kf5>g(oDqDI4C{;Bv162k8sFNr;!qPa2LPh>=1n z=^_9)TsLDvTqK7&*Vfm5k;VXjBW^qN3Tl&}K=X5)oXJs$z3gk0_+7`mJvz{pK|FVs zHw!k&7xVjvY;|(Py<;J{)b#Yjj*LZO7x|~pO4^MJ2LqK3X;Irb%nf}L|gck zE#55_BNsy6m+W{e zo!P59DDo*s@VIi+S|v93PwY6d?CE=S&!JLXwE9{i)DMO*_X90;n2*mPDrL%{iqN!?%-_95J^L z=l<*{em(6|h7DR4+4G3Wr;4*}yrBkbe3}=p7sOW1xj!EZVKSMSd;QPw>uhKK z#>MlS@RB@-`ULv|#zI5GytO{=zp*R__uK~R6&p$q{Y{iNkg61yAgB8C^oy&``{~FK z8hE}H&nIihSozKrOONe5Hu?0Zy04U#0$fB7C6y~?8{or}KNvP)an=QP&W80mj&8WL zEZQF&*FhoMMG6tOjeiCIV;T{I>jhi9hiUwz?bkX3NS-k5eWKy)Mo_orMEg4sV6R6X&i-Q%JG;Esl+kLpn@Bsls9O|i9z`tKB^~1D5)RIBB&J<6T@a4$pUvh$IR$%ubH)joi z!7>ON0DPwx=>0DA>Bb^c?L8N0BBrMl#oDB+GOXJh;Y&6I)#GRy$W5xK%a;KS8BrER zX)M>Rdoc*bqP*L9DDA3lF%U8Yzb6RyIsW@}IKq^i7v&{LeIc=*ZHIbO68x=d=+0T( zev=DT9f|x!IWZNTB#N7}V4;9#V$%Wo0%g>*!MdLOEU>My0^gni9ocID{$g9ytD!gy zKRWT`DVN(lcYjR|(}f0?zgBa3SwunLfAhx><%u0uFkrdyqlh8_g zDKt#R6rA2(Vm2LW_>3lBNYKG_F{TEnnKWGGC15y&OebIRhFL4TeMR*v9i0wPoK#H< zu4){s4K&K)K(9~jgGm;H7lS7y_RYfS;&!Oj5*eqbvEcW^a*i67nevzOZxN6F+K~A%TYEtsAVsR z@J=1hc#Dgs7J2^FL|qV&#WBFQyDtEQ2kPO7m2`)WFhqAob)Y>@{crkil6w9VoA?M6 zADGq*#-hyEVhDG5MQj677XmcWY1_-UO40QEP&+D)rZoYv^1B_^w7zAvWGw&pQyCyx zD|ga$w!ODOxxGf_Qq%V9Z7Q2pFiUOIK818AGeZ-~*R zI1O|SSc=3Z?#61Rd|AXx2)K|F@Z1@x!hBBMhAqiU)J=U|Y)T$h3D?ZPPQgkSosnN! zIqw-t$0fqsOlgw3TlHJF*t$Q@bg$9}A3X=cS@-yU3_vNG_!#9}7=q7!LZ?-%U26W4 z$d>_}*s1>Ac%3uFR;tnl*fNlylJ)}r2^Q3&@+is3BIv<}x>-^_ng;jhdaM}6Sg3?p z0jS|b%QyScy3OQ(V*~l~bK>VC{9@FMuW_JUZO?y(V?LKWD6(MXzh}M3r3{7b4eB(#`(q1m{>Be%_<9jw8HO!x#yF6vez$c#kR+}s zZO-_;25Sxngd(}){zv?ccbLqRAlo;yog>4LH&uZUK1n>x?u49C)Y&2evH5Zgt~666 z_2_z|H5AO5Iqxv_Bn~*y1qzRPcob<+Otod5Xd2&z=C;u+F}zBB@b^UdGdUz|s!H}M zXG%KiLzn3G?FZgdY&3pV$nSeY?ZbU^jhLz9!t0K?ep}EFNqR1@E!f*n>x*!uO*~JF zW9UXWrVgbX1n#76_;&0S7z}(5n-bqnII}_iDsNqfmye@)kRk`w~1 z6j4h4BxcPe6}v)xGm%=z2#tB#^KwbgMTl2I*$9eY|EWAHFc3tO48Xo5rW z5oHD!G4kb?MdrOHV=A+8ThlIqL8Uu+7{G@ zb)cGBm|S^Eh5= z^E^SZ=yeC;6nNCdztw&TdnIz}^Of@Ke*@vjt)0g>Y!4AJvWiL~e7+9#Ibhe)> ziNwh>gWZL@FlWc)wzihocz+%+@*euwXhW%Hb>l7tf8aJe5_ZSH1w-uG|B;9qpcBP0 zM`r1Hu#htOl)4Cl1c7oY^t0e4Jh$-I(}M5kzWqh{F=g&IM#JiC`NDSd@BCKX#y<P@Gwl$3a3w z6<(b|K(X5FIR22M)sy$4jY*F4tT{?wZRI+KkZFb<@j@_C316lu1hq2hA|1wCmR+S@ zRN)YNNE{}i_H`_h&VUT5=Y(lN%m?%QX;6$*1P}K-PcPx>*S55v)qZ@r&Vcic-sjkm z! z=nfW&X`}iAqa_H$H%z3Tyz5&P3%+;93_0b;zxLs)t#B|up}JyV$W4~`8E@+BHQ+!y zuIo-jW!~)MN$2eHwyx-{fyGjAWJ(l8TZtUp?wZWBZ%}krT{f*^fqUh+ywHifw)_F> zp76_kj_B&zFmv$FsPm|L7%x-j!WP>_P6dHnUTv!9ZWrrmAUteBa`rT7$2ixO;ga8U z3!91micm}{!Btk+I%pMgcKs?H4`i+=w0@Ws-CS&n^=2hFTQ#QeOmSz6ttIkzmh^`A zYPq)G1l3h(E$mkyr{mvz*MP`x+PULBn%CDhltKkNo6Uqg!vJ#DA@BIYr9TQ`18Un2 zv$}BYzOQuay9}w(?JV63F$H6WmlYPPpH=R|CPb%C@BCv|&Q|&IcW7*LX?Q%epS z`=CPx{1HnJ9_46^=0VmNb>8JvMw-@&+V8SDLRYsa>hZXEeRbtf5eJ>0@Ds47zIY{N z42EOP9J8G@MXXdeiPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91AfN*P1ONa40RR91AOHXW0IY^$^8f$?lu1NER9Fe^SItioK@|V(ZWmgL zZT;XwPgVuWM>O%^|Dc$VK;n&?9!&g5)aVsG8cjs5UbtxVVnQNOV~7Mrg3+jnU;rhE z6fhW6P)R>_eXrXo-RW*y6RQ_qcb^s1wTu$TwriZ`=JUws>vRi}5x}MW1MR#7p|gIWJlaLK;~xaN}b< z<-@=RX-%1mt`^O0o^~2=CD7pJ<<$Rp-oUL-7PuG>do^5W_Mk#unlP}6I@6NPxY`Q} zuXJF}!0l)vwPNAW;@5DjPRj?*rZxl zwn;A(cFV!xe^CUu+6SrN?xe#mz?&%N9QHf~=KyK%DoB8HKC)=w=3E?1Bqj9RMJs3U z5am3Uv`@+{jgqO^f}Lx_Jp~CoP3N4AMZr~4&d)T`R?`(M{W5WWJV^z~2B|-oih@h^ zD#DuzGbl(P5>()u*YGo*Och=oRr~3P1wOlKqI)udc$|)(bacG5>~p(y>?{JD7nQf_ z*`T^YL06-O>T(s$bi5v~_fWMfnE7Vn%2*tqV|?~m;wSJEVGkNMD>+xCu#um(7}0so zSEu7?_=Q64Q5D+fz~T=Rr=G_!L*P|(-iOK*@X8r{-?oBlnxMNNgCVCN9Y~ocu+?XA zjjovJ9F1W$Nf!{AEv%W~8oahwM}4Ruc+SLs>_I_*uBxdcn1gQ^2F8a*vGjgAXYyh? zWCE@c5R=tbD(F4nL9NS?$PN1V_2*WR?gjv3)4MQeizuH`;sqrhgykEzj z593&TGlm3h`sIXy_U<7(dpRXGgp0TB{>s?}D{fwLe>IV~exweOfH!qM@CV5kib!YA z6O0gvJi_0J8IdEvyP#;PtqP*=;$iI2t(xG2YI-e!)~kaUn~b{6(&n zp)?iJ`z2)Xh%sCV@BkU`XL%_|FnCA?cVv@h*-FOZhY5erbGh)%Q!Av#fJM3Csc_g zC2I6x%$)80`Tkz#KRA!h1FzY`?0es3t!rKDT5EjPe6B=BLPr7s0GW!if;Ip^!AmGW zL;$`Vdre+|FA!I4r6)keFvAx3M#1`}ijBHDzy)3t0gwjl|qC2YB`SSxFKHr(oY#H$)x{L$LL zBdLKTlsOrmb>T0wd=&6l3+_Te>1!j0OU8%b%N342^opKmT)gni(wV($s(>V-fUv@0p8!f`=>PxC|9=nu ze{ToBBj8b<{PLfXV$h8YPgA~E!_sF9bl;QOF{o6t&JdsX?}rW!_&d`#wlB6T_h;Xf zl{4Tz5>qjF4kZgjO7ZiLPRz_~U@k5%?=30+nxEh9?s78gZ07YHB`FV`4%hlQlMJe@J`+e(qzy+h(9yY^ckv_* zb_E6o4p)ZaWfraIoB2)U7_@l(J0O%jm+Or>8}zSSTkM$ASG^w3F|I? z$+eHt7T~04(_WfKh27zqS$6* zzyy-ZyqvSIZ0!kkSvHknm_P*{5TKLQs8S6M=ONuKAUJWtpxbL#2(_huvY(v~Y%%#~ zYgsq$JbLLprKkV)32`liIT$KKEqs$iYxjFlHiRNvBhxbDg*3@Qefw4UM$>i${R5uB zhvTgmqQsKA{vrKN;TSJU2$f9q=y{$oH{<)woSeV>fkIz6D8@KB zf4M%v%f5U2?<8B(xn}xV+gWP?t&oiapJhJbfa;agtz-YM7=hrSuxl8lAc3GgFna#7 zNjX7;`d?oD`#AK+fQ=ZXqfIZFEk{ApzjJF0=yO~Yj{7oQfXl+6v!wNnoqwEvrs81a zGC?yXeSD2NV!ejp{LdZGEtd1TJ)3g{P6j#2jLR`cpo;YX}~_gU&Gd<+~SUJVh+$7S%`zLy^QqndN<_9 zrLwnXrLvW+ew9zX2)5qw7)zIYawgMrh`{_|(nx%u-ur1B7YcLp&WFa24gAuw~& zKJD3~^`Vp_SR$WGGBaMnttT)#fCc^+P$@UHIyBu+TRJWbcw4`CYL@SVGh!X&y%!x~ zaO*m-bTadEcEL6V6*{>irB8qT5Tqd54TC4`h`PVcd^AM6^Qf=GS->x%N70SY-u?qr>o2*OV7LQ=j)pQGv%4~z zz?X;qv*l$QSNjOuQZ>&WZs2^@G^Qas`T8iM{b19dS>DaXX~=jd4B2u`P;B}JjRBi# z_a@&Z5ev1-VphmKlZEZZd2-Lsw!+1S60YwW6@>+NQ=E5PZ+OUEXjgUaXL-E0fo(E* zsjQ{s>n33o#VZm0e%H{`KJi@2ghl8g>a~`?mFjw+$zlt|VJhSU@Y%0TWs>cnD&61fW4e0vFSaXZa4-c}U{4QR8U z;GV3^@(?Dk5uc@RT|+5C8-24->1snH6-?(nwXSnPcLn#X_}y3XS)MI_?zQ$ZAuyg+ z-pjqsw}|hg{$~f0FzmmbZzFC0He_*Vx|_uLc!Ffeb8#+@m#Z^AYcWcZF(^Os8&Z4g zG)y{$_pgrv#=_rV^D|Y<_b@ICleUv>c<0HzJDOsgJb#Rd-Vt@+EBDPyq7dUM9O{Yp zuGUrO?ma2wpuJuwl1M=*+tb|qx7Doj?!F-3Z>Dq_ihFP=d@_JO;vF{iu-6MWYn#=2 zRX6W=`Q`q-+q@Db|6_a1#8B|#%hskH82lS|9`im0UOJn?N#S;Y0$%xZw3*jR(1h5s z?-7D1tnIafviko>q6$UyqVDq1o@cwyCb*})l~x<@s$5D6N=-Uo1yc49p)xMzxwnuZ zHt!(hu-Ek;Fv4MyNTgbW%rPF*dB=;@r3YnrlFV{#-*gKS_qA(G-~TAlZ@Ti~Yxw;k za1EYyX_Up|`rpbZ0&Iv#$;eC|c0r4XGaQ-1mw@M_4p3vKIIpKs49a8Ns#ni)G314Z z8$Ei?AhiT5dQGWUYdCS|IC7r z=-8ol>V?u!n%F*J^^PZ(ONT&$Ph;r6X;pj|03HlDY6r~0g~X#zuzVU%a&!fs_f|m?qYvg^Z{y?9Qh7Rn?T*F%7lUtA6U&={HzhYEzA`knx1VH> z{tqv?p@I(&ObD5L4|YJV$QM>Nh-X3cx{I&!$FoPC_2iIEJfPk-$;4wz>adRu@n`_y z_R6aN|MDHdK;+IJmyw(hMoDCFCQ(6?hCAG5&7p{y->0Uckv# zvooVuu04$+pqof777ftk<#42@KQ((5DPcSMQyzGOJ{e9H$a9<2Qi_oHjl{#=FUL9d z+~0^2`tcvmp0hENwfHR`Ce|<1S@p;MNGInXCtHnrDPXCKmMTZQ{HVm_cZ>@?Wa6}O zHsJc7wE)mc@1OR2DWY%ZIPK1J2p6XDO$ar`$RXkbW}=@rFZ(t85AS>>U0!yt9f49^ zA9@pc0P#k;>+o5bJfx0t)Lq#v4`OcQn~av__dZ-RYOYu}F#pdsl31C^+Qgro}$q~5A<*c|kypzd} ziYGZ~?}5o`S5lw^B{O@laad9M_DuJle- z*9C7o=CJh#QL=V^sFlJ0c?BaB#4bV^T(DS6&Ne&DBM_3E$S^S13qC$7_Z?GYXTpR@wqr70wu$7+qvf-SEUa5mdHvFbu^7ew!Z1a^ zo}xKOuT*gtGws-a{Tx}{#(>G~Y_h&5P@Q8&p!{*s37^QX_Ibx<6XU*AtDOIvk|^{~ zPlS}&DM5$Ffyu-T&0|KS;Wnaqw{9DB&B3}vcO14wn;)O_e@2*9B&0I_ zZz{}CMxx`hv-XouY>^$Y@J(_INeM>lIQI@I>dBAqq1)}?Xmx(qRuX^i4IV%=MF306 z9g)i*79pP%_7Ex?m6ag-4Tlm=Z;?DQDyC-NpUIb#_^~V_tsL<~5<&;Gf2N+p?(msn zzUD~g>OoW@O}y0@Z;RN)wjam`CipmT&O7a|YljZqU=U86 zedayEdY)2F#BJ6xvmW8K&ffdS*0!%N<%RB!2~PAT4AD*$W7yzHbX#Eja9%3aD+Ah2 zf#T;XJW-GMxpE=d4Y>}jE=#U`IqgSoWcuvgaWQ9j1CKzG zDkoMDDT)B;Byl3R2PtC`ip=yGybfzmVNEx{xi_1|Cbqj>=FxQc{g`xj6fIfy`D8fA z##!-H_e6o0>6Su&$H2kQTujtbtyNFeKc}2=|4IfLTnye#@$Au7Kv4)dnA;-fz@D_8 z)>irG$)dkBY~zX zC!ZXLy*L3xr6cb70QqfN#Q>lFIc<>}>la4@3%7#>a1$PU&O^&VszpxLC%*!m-cO{B z-Y}rQr4$84(hvy#R69H{H zJ*O#uJh)TF6fbXy;fZkk%X=CjsTK}o5N1a`d7kgYYZLPxsHx%9*_XN8VWXEkVJZ%A z1A+5(B;0^{T4aPYr8%i@i32h)_)|q?9vws)r+=5u)1YNftF5mknwfd*%jXA2TeP}Z zQ!m?xJ3?9LpPM?_A3$hQ1QxNbR&}^m z!F999s?p^ak#C4NM_x2p9FoXWJ$>r?lJ)2bG)sX{gExgLA2s5RwHV!h6!C~d_H||J z>9{E{mEv{Z1z~65Vix@dqM4ZqiU|!)eWX$mwS5mLSufxbpBqqS!jShq1bmwCR6 z4uBri7ezMeS6ycaXPVu(i2up$L; zjpMtB`k~WaNrdgM_R=e#SN?Oa*u%nQy01?()h4A(jyfeNfx;5o+kX?maO4#1A^L}0 zYNyIh@QVXIFiS0*tE}2SWTrWNP3pH}1Vz1;E{@JbbgDFM-_Mky^7gH}LEhl~Ve5PexgbIyZ(IN%PqcaV@*_`ZFb=`EjspSz%5m2E34BVT)d=LGyHVz@-e%9Ova*{5@RD;7=Ebkc2GP%pIP^P7KzKapnh`UpH?@h z$RBpD*{b?vhohOKf-JG3?A|AX|2pQ?(>dwIbWhZ38GbTm4AImRNdv_&<99ySX;kJ| zo|5YgbHZC#HYgjBZrvGAT4NZYbp}qkVSa;C-LGsR26Co+i_HM&{awuO9l)Ml{G8zD zs$M8R`r+>PT#Rg!J(K6T4xHq7+tscU(}N$HY;Yz*cUObX7J7h0#u)S7b~t^Oj}TBF zuzsugnst;F#^1jm>22*AC$heublWtaQyM6RuaquFd8V#hJ60Z3j7@bAs&?dD#*>H0SJaDwp%U~27>zdtn+ z|8sZzklZy$%S|+^ie&P6++>zbrq&?+{Yy11Y>@_ce@vU4ZulS@6yziG6;iu3Iu`M= zf3rcWG<+3F`K|*(`0mE<$89F@jSq;j=W#E>(R}2drCB7D*0-|D;S;(;TwzIJkGs|q z2qH{m_zZ+el`b;Bv-#bQ>}*VPYC|7`rgBFf2oivXS^>v<&HHTypvd4|-zn|=h=TG{ z05TH2+{T%EnADO>3i|CB zCu60#qk`}GW{n4l-E$VrqgZGbI zbQW690KgZt4U3F^5@bdO1!xu~p@7Y~*_FfWg2CdvED5P5#w#V46LH`<&V0{t&Ml~4 zHNi7lIa+#i+^Z6EnxO7KJQw)wD)4~&S-Ki8)3=jpqxmx6c&zU&<&h%*c$I(5{1HZT zc9WE}ijcWJiVa^Q^xC|WX0habl89qycOyeViIbi(LFsEY_8a|+X^+%Qv+W4vzj>`y zpuRnjc-eHNkvXvI_f{=*FX=OKQzT?bck#2*qoKTHmDe>CDb&3AngA1O)1b}QJ1Tun z_<@yVEM>qG7664Pa@dzL@;DEh`#?yM+M|_fQS<7yv|i*pw)|Z8)9IR+QB7N3v3K(wv4OY*TXnH&X0nQB}?|h2XQeGL^q~N7N zDFa@x0E(UyN7k9g%IFq7Sf+EAfE#K%%#`)!90_)Dmy3Bll&e1vHQyPA87TaF(xbqMpDntVp?;8*$87STop$!EAnGhZ?>mqPJ(X zFsr336p3P{PpZCGn&^LP(JjnBbl_3P3Kcq+m}xVFMVr1zdCPJMDIV_ki#c=vvTwbU z*gKtfic&{<5ozL6Vfpx>o2Tts?3fkhWnJD&^$&+Mh5WGGyO7fG@6WDE`tEe(8<;+q z@Ld~g08XDzF8xtmpIj`#q^(Ty{Hq>t*v`pedHnuj(0%L(%sjkwp%s}wMd!a<*L~9T z9MM@s)Km~ogxlqEhIw5(lc46gCPsSosUFsgGDr8H{mj%OzJz{N#;bQ;KkV+ZWA1(9 zu0PXzyh+C<4OBYQ0v3z~Lr;=C@qmt8===Ov2lJ1=DeLfq*#jgT{YQCuwz?j{&3o_6 zsqp2Z_q-YWJg?C6=!Or|b@(zxTlg$ng2eUQzuC<+o)k<6^9ju_Z*#x+oioZ5T8Z_L zz9^A1h2eFS0O5muq8;LuDKwOv4A9pxmOjgb6L*i!-(0`Ie^d5Fsgspon%X|7 zC{RRXEmYn!5zP9XjG*{pLa)!2;PJB2<-tH@R7+E1cRo=Wz_5Ko8h8bB$QU%t9#vol zAoq?C$~~AsYC|AQQ)>>7BJ@{Cal)ZpqE=gjT+Juf!RD-;U0mbV1ED5PbvFD6M=qj1 zZ{QERT5@(&LQ~1X9xSf&@%r|3`S#ZCE=sWD`D4YQZ`MR`G&s>lN{y2+HqCfvgcw3E z-}Kp(dfGG?V|97kAHQX+OcKCZS`Q%}HD6u*e$~Ki&Vx53&FC!x94xJd4F2l^qQeFO z?&JdmgrdVjroKNJx64C!H&Vncr^w zzR#XI}Dn&o8jB~_YlVM^+#0W(G1LZH5K^|uYT@KSR z^Y5>^*Bc45E1({~EJB(t@4n9gb-eT#s@@7)J^^<_VV`Pm!h7av8XH6^5zO zOcQBhTGr;|MbRsgxCW69w{bl4EW#A~);L?d4*y#j8Ne=Z@fmJP0k4{_cQ~KA|Y#_#BuUiYx8y*za3_6Y}c=GSe7(2|KAfhdzud!Zq&}j)=o4 z7R|&&oX7~e@~HmyOOsCCwy`AR+deNjZ3bf6ijI_*tKP*_5JP3;0d;L_p(c>W1b%sG zJ*$wcO$ng^aW0E(5ldckV9unU7}OB7s?Wx(761?1^&8tA5y0_(ieV>(x-e@}1`lWC z-YH~G$D>#ud!SxK2_Iw{K%92=+{4yb-_XC>ji&j7)1ofp(OGa4jjF;Hd*`6YQL+Jf zffg+6CPc8F@EDPN{Kn96yip;?g@)qgkPo^nVKFqY?8!=h$G$V=<>%5J&iVjwR!7H0 z$@QL|_Q81I;Bnq8-5JyNRv$Y>`sWl{qhq>u+X|)@cMlsG!{*lu?*H`Tp|!uv z9oEPU1jUEj@ueBr}%Y)7Luyi)REaJV>eQ{+uy4uh0ep0){t;OU8D*RZ& zE-Z-&=BrWQLAD^A&qut&4{ZfhqK1ZQB0fACP)=zgx(0(o-`U62EzTkBkG@mXqbjXm z>w`HNeQM?Is&4xq@BB(K;wv5nI6EXas)XXAkUuf}5uSrZLYxRCQPefn-1^#OCd4aO zzF=dQ*CREEyWf@n6h7(uXLNgJIwGp#Xrsj6S<^bzQ7N0B0N{XlT;`=m9Olg<>KL}9 zlp>EKTx-h|%d1Ncqa=wnQEuE;sIO-f#%Bs?g4}&xS?$9MG?n$isHky0caj za8W+B^ERK#&h?(x)7LLpOqApV5F>sqB`sntV%SV>Q1;ax67qs+WcssfFeF3Xk=e4^ zjR2^(%K1oBq%0%Rf!y&WT;lu2Co(rHi|r1_uW)n{<7fGc-c=ft7Z0Q}r4W$o$@tQF#i?jDBwZ8h+=SC}3?anUp3mtRVv9l#H?-UD;HjTF zQ*>|}e=6gDrgI9p%c&4iMUkQa4zziS$bO&i#DI$Wu$7dz7-}XLk%!US^XUIFf2obO zFCTjVEtkvYSKWB;<0C;_B{HHs~ax_48^Cml*mjfBC5*7^HJZiLDir(3k&BerVIZF8zF;0q80eX8c zPN4tc+Dc5DqEAq$Y3B3R&XPZ=AQfFMXv#!RQnGecJONe0H;+!f^h5x0wS<+%;D}MpUbTNUBA}S2n&U59-_5HKr{L^jPsV8B^%NaH|tUr)mq=qCBv_- ziZ1xUp(ZzxUYTCF@C}To;u60?RIfTGS?#JnB8S8@j`TKPkAa)$My+6ziGaBcA@){d z91)%+v2_ba7gNecdj^8*I4#<11l!{XKl6s0zkXfJPxhP+@b+5ev{a>p*W-3*25c&} zmCf{g9mPWVQ$?Sp*4V|lT@~>RR)9iNdN^7KT@>*MU3&v^3e?=NTbG9!h6C|9zO097 zN{Qs6YwR-5$)~ z`b~qs`a1Dbx8P>%V=1XGjBptMf%P~sl1qbHVm1HYpY|-Z^Dar8^HqjIw}xaeRlsYa zJ_@Apy-??`gxPmb`m`0`z`#G7*_C}qiSZe~l2z65tE~IwMw$1|-u&t|z-8SxliH00 zlh1#kuqB56s+E&PWQ7Nz17?c}pN+A@-c^xLqh(j;mS|?>(Pf7(?qd z5q@jkc^nA&!K-}-1P=Ry0yyze0W!+h^iW}7jzC1{?|rEFFWbE^Yu7Y}t?jmP-D$f+ zmqFT7nTl0HL|4jwGm7w@a>9 zKD)V~+g~ysmei$OT5}%$&LK8?ib|8aY|>W3;P+0B;=oD=?1rg+PxKcP(d;OEzq1CKA&y#boc51P^ZJPPS)z5 zAZ)dd2$glGQXFj$`XBBJyl2y-aoBA8121JC9&~|_nY>nkmW>TLi%mWdn-^Jks-Jv| zSR*wij;A3Fcy8KsDjQ15?Z9oOj|Qw2;jgJiq>dxG(2I2RE- z$As!#zSFIskebqU2bnoM^N<4VWD2#>!;saPSsY8OaCCQqkCMdje$C?Sp%V}f2~tG5 z0whMYk6tcaABwu*x)ak@n4sMElGPX1_lmv@bgdI2jPdD|2-<~Jf`L`@>Lj7{<-uLQ zE3S_#3e10q-ra=vaDQ42QUY^@edh>tnTtpBiiDVUk5+Po@%RmuTntOlE29I4MeJI?;`7;{3e4Qst#i-RH6s;>e(Sc+ubF2_gwf5Qi%P!aa89fx6^{~A*&B4Q zKTF|Kx^NkiWx=RDhe<{PWXMQ;2)=SC=yZC&mh?T&CvFVz?5cW~ritRjG2?I0Av_cI z)=s!@MXpXbarYm>Kj0wOxl=eFMgSMc?62U#2gM^li@wKPK9^;;0_h7B>F>0>I3P`{ zr^ygPYp~WVm?Qbp6O3*O2)(`y)x>%ZXtztz zMAcwKDr=TCMY!S-MJ8|2MJCVNUBI0BkJV6?(!~W!_dC{TS=eh}t#X+2D>Kp&)ZN~q zvg!ogxUXu^y(P*;Q+y_rDoGeSCYxkaGPldDDx)k;ocJvvGO#1YKoQLHUf2h_pjm&1 zqh&!_KFH03FcJvSdfgUYMp=5EpigZ*8}7N_W%Ms^WSQ4hH`9>3061OEcxmf~TcYn5_oHtscWn zo5!ayj<_fZ)vHu3!A!7M;4y1QIr8YGy$P2qDD_4+T8^=^dB6uNsz|D>p~4pF3Nrb6 zcpRK*($<~JUqOya#M1=#IhOZ zG)W+rJS-x(6EoVz)P zsSo>JtnChdj9^);su%SkFG~_7JPM zEDz3gk2T7Y%x>1tWyia|op(ilEzvAujW?Xwlw>J6d7yEi8E zv30riR|a_MM%ZZX&n!qm0{2agq(s?x9E@=*tyT$nND+{Djpm7Rsy!+c$j+wqMwTOF zZL8BQ|I`<^bGW)5apO{lh(Asqen?_U`$_n0-Ob~Yd%^89oEe%9yGumQ_8Be+l2k+n zCxT%s?bMpv|AdWP7M1LQwLm|x+igA~;+iK-*+tClF&ueX_V}>=4gvZ01xpubQWXD_ zi?Un>&3=$fu)dgk-Z;0Ll}HK5_YM->l^Czrd0^cJ))(DwL2g3aZuza7ga9^|mT_70 z))}A}r1#-(9cxtn<9jGRwOB4hb9kK@YCgjfOM-90I$8@l=H^`K$cyhe2mTM|FY9vW znH~h)I<_aa#V1xmhk?Ng@$Jw-s%a!$BI4Us+Df+?J&gKAF-M`v}j`OWKP3>6`X`tEmhe#y*(Xm$_^Ybbs=%;L7h zp7q^C*qM}Krqsinq|WolR99>_!GL#Z71Hhz|IwQQv<>Ds09B?Je(lhI1(FInO8mc} zl$RyKCUmfku+Cd^8s0|t+e}5g7M{ZPJQH=UB3(~U&(w#Bz#@DTDHy>_UaS~AtN>4O zJ-I#U@R($fgupHebcpuEBX`SZ>kN!rW$#9>s{^3`86ZRQRtYTY)hiFm_9wU3c`SC8 z-5M%g)h}3Pt|wyj#F%}pGC@VL`9&>9P+_UbudCkS%y2w&*o})hBplrB*@Z?gel5q+ z%|*59(sR9GMk3xME}wd%&k?7~J)OL`rK#4d-haC7uaU8-L@?$K6(r<0e<;y83rK&` z3Q!1rD9WkcB8WBQ|WT|$u^lkr0UL4WH4EQTJyk@5gzHb18cOte4w zS`fLv8q;PvAZyY;*Go3Qw1~5#gP0D0ERla6M6#{; zr1l?bR}Nh+OC7)4bfAs(0ZD(axaw6j9v`^jh5>*Eo&$dAnt?c|Y*ckEORIiJXfGcM zEo`bmIq6rJm`XhkXR-^3d8^RTK2;nmVetHfUNugJG(4XLOu>HJA;0EWb~?&|0abr6 zxqVp@p=b3MN^|~?djPe!=eex(u!x>RYFAj|*T$cTi*Sd3Bme7Pri1tkK9N`KtRmXf zZYNBNtik97ct1R^vamQBfo9ZUR@k*LhIg8OR9d_{iv#t)LQV91^5}K5u{eyxwOFoU zHMVq$C>tfa@uNDW^_>EmO~WYQd(@!nKmAvSSIb&hPO|}g-3985t?|R&WZXvxS}Kt2i^eRe>WHb_;-K5cM4=@AN1>E&1c$k!w4O*oscx(f=<1K6l#8Exi)U(ZiZ zdr#YTP6?m1e1dOKysUjQ^>-MR={OuD00g6+(a^cvcmn#A_%Fh3Of%(qP5nvjS1=(> z|Ld8{u%(J}%2SY~+$4pjy{()5HN2MYUjg1X9umxOMFFPdM+IwOVEs4Z(olynvT%G) zt9|#VR}%O2@f6=+6uvbZv{3U)l;C{tuc zZ{K$rut=eS%3_~fQv^@$HV6#9)K9>|0qD$EV2$G^XUNBLM|5-ZmFF!KV)$4l^KVj@ zZ4fI}Knv*K%zPqK77}B-h_V{66VrmoZP2>@^euu8Rc}#qwRwt5uEBWcJJE5*5rT2t zA4Jpx`QQ~1Sh_n_a9x%Il!t1&B~J6p54zxAJx`REov${jeuL8h8x-z=?qwMAmPK5i z_*ES)BW(NZluu#Bmn1-NUKQip_X&_WzJy~J`WYxEJQ&Gu7DD< z&F9urE;}8S{x4{yB zaq~1Zrz%8)<`prSQv$eu5@1RY2WLu=waPTrn`WK%;G5(jt^FeM;gOdvXQjYhax~_> z{bS_`;t#$RYMu-;_Dd&o+LD<5Afg6v{NK?0d8dD5ohAN?QoocETBj?y{MB)jQ%UQ}#t3j&iL!qr@#6JEajR3@^k5wgLfI9S9dT2^f`2wd z%I#Q*@Ctk@w=(u)@QC}yBvUP&fFRR-uYKJ){Wp3&$s(o~W7OzgsUIPx0|ph2L1(r*_Pa@T@mcH^JxBjh09#fgo|W#gG7}|)k&uD1iZxb0 z@|Y)W79SKj9sS&EhmTD;uI#)FE6VwQ*YAr&foK$RI5H8_ripb$^=;U%gWbrrk4!5P zXDcyscEZoSH~n6VJu8$^6LE6)>+=o#Q-~*jmob^@191+Ot1w454e3)WMliLtY6~^w zW|n#R@~{5K#P+(w+XC%(+UcOrk|yzkEes=!qW%imu6>zjdb!B#`efaliKtN}_c!Jp zfyZa`n+Nx8;*AquvMT2;c8fnYszdDA*0(R`bsof1W<#O{v%O!1IO4WZe=>XBu_D%d zOwWDaEtX%@B>4V%f1+dKqcXT>m2!|&?}(GK8e&R=&w?V`*Vj)sCetWp9lr@@{xe6a zE)JL&;p}OnOO}Nw?vFyoccXT*z*?r}E8{uPtd;4<(hmX;d$rqJhEF}I+kD+m(ke;J z7Cm$W*CSdcD=RYEBhedg>tuT{PHqwCdDP*NkHv4rvQTXkzEn*Mb0oJz&+WfWIOS4@ zzpPJ|e%a-PIwOaOC7uQcHQ-q(SE(e@fj+7oC@34wzaBNaP;cw&gm{Z8yYX?V(lIv5 zKbg*zo1m5aGA4^lwJ|bAU=j3*d8S{vp!~fLFcK8s6%Ng55_qW_d*3R%e=34aDZPfD z&Le39j|ahp6E7B0*9OVdeMNrTErFatiE+=Z!XZ^tv0y%zZKXRTBuPyP&C{5(H?t)S zKV24_-TKpOmCPzU&by8R1Q5HY^@IDoeDA9MbgizgQ*F1Er~HVmvSU>vx}pZVQ&tr| zOtZl8vfY2#L<)gZ=ba&wG~EI*Vd?}lRMCf+!b5CDz$8~be-HKMo5omk$w7p4`Mym*IR8WiTz4^kKcUo^8Hkcsu14u z`Pkg`#-Y^A%CqJ0O@UF|caAulf68@(zhqp~YjzInh7qSN7Ov%Aj(Qz%{3zW|xubJ- ztNE_u_MO7Q_585r;xD?e=Er}@U1G@BKW5v$UM((eByhH2p!^g9W}99OD8VV@7d{#H zv)Eam+^K(5>-Ot~U!R$Um3prQmM)7DyK=iM%vy>BRX4#aH7*oCMmz07YB(EL!^%F7?CA#>zXqiYDhS;e?LYPTf(bte6B ztrfvDXYG*T;ExK-w?Knt{jNv)>KMk*sM^ngZ-WiUN;=0Ev^GIDMs=AyLg2V@3R z7ugNc45;4!RPxvzoT}3NCMeK$7j#q3r_xV(@t@OPRyoKBzHJ#IepkDsm$EJRxL)A* zf{_GQYttu^OXr$jHQn}zs$Eh|s|Z!r?Yi+bS-bi+PE*lH zo|6ztu6$r_?|B~S#m>imI!kQP9`6X426uHRri!wGcK;J;`%sFM(D#*Le~W*t2uH`Q z(HEO9-c_`mhA@4QhbW+tgtt9Pzx=_*3Kh~TB$SKmU4yx-Ay&)n%PZPKg#rD4H{%Ke zdMY@rf5EAFfqtrf?Vmk&N(_d-<=bvfOdPrYwY*;5%j@O6@O#Qj7LJTk-x3LN+dEKy+X z>~U8j3Ql`exr1jR>+S4nEy+4c2f{-Q!3_9)yY758tLGg7k^=nt<6h$YE$ltA+13S<}uOg#XHe6 zZHKdNsAnMQ_RIuB;mdoZ%RWpandzLR-BnjN2j@lkBbBd+?i ze*!5mC}!Qj(Q!rTu`KrRRqp22c=hF6<^v&iCDB`n7mHl;vdclcer%;{;=kA(PwdGG zdX#BWoC!leBC4);^J^tPkPbIe<)~nYb6R3u{HvC!NOQa?DC^Q`|_@ zcz;rk`a!4rSLAS>_=b@g?Yab4%=J3Cc7pRv8?_rHMl_aK*HSPU%0pG2Fyhef_biA!aW|-(( z*RIdG&Lmk(=(nk28Q1k1Oa$8Oa-phG%Mc6dT3>JIylcMMIc{&FsBYBD^n@#~>C?HG z*1&FpYVvXOU@~r2(BUa+KZv;tZ15#RewooEM0LFb>guQN;Z0EBFMFMZ=-m$a3;gVD z)2EBD4+*=6ZF?+)P`z@DOT;azK0Q4p4>NfwDR#Pd;no|{q_qB!zk1O8QojE;>zhPu z1Q=1z^0MYHo1*``H3ex|bW-Zy==5J4fE2;g6sq6YcXMYK5i|S^9(OSw#v!3^!EB<% zZF~J~CleS`V-peStyf*I%1^R88D;+8{{qN6-t!@gTARDg^w2`uSzFZbPQ!)q^oC}m zPo8VOQxq2BaIN`pAVFGu8!{p3}(+iZ`f4ck2ygVpEZMQW38nLpj3NQx+&sAkb8`}P3- zc>N*k6AG?r}bfO6_vccTuKX+*- z7W4Q#2``P0jIHYs)F>uG#AM#I6W2)!Nu2nD5{CRV_PmkDS2ditmbd#pggqEgAo%5oC?|CP zGa0CV)wA*ko!xC7pZYkqo{10CN_e00FX5SjWkI3?@XG}}bze!(&+k2$C-C`6temSk z_YyYpB^wh3woo`B zrMSTd4T?(X-jh`FeO76C(3xsOm9s2BP_b%ospg^!#*2*o9N;tf4(X9$qc_d(()yz5 zDk@1}u_Xd+86vy5RBs?LQCuYKCGPS;E4uFOi@V%1JTK&|eRf~lp$AV#;*#O}iRI2=i3rFL8{ zA^ptDZ0l6k-mq=hUJ0x$Y@J>UNfz~I5l63H(`~*v;qX`Z{zwsQQD-!wp0D&hyB8&Z z7$R07gIKGJ^%AvQ{4KM0edM39iFRx=P^6`!<1(s0t|JbB2tXs_B_IH9#ajH0C=-n+ z`nz`fKMBKLlf?2AC+|83M+0rqR%uhNGD;uKA6jOjp7YDe^4%0fRB<^bcjlS2KF~F; zu09wh1x0&4pG&76M;x8$u`b134t=dEPBn6PV|X29<#T4F1mxGF*HOgiWU8tN@cguI z_F@o+XL7FJztR63wC|j4x_DANzcX94r7Iz-O2x$({&qd*mdLG=-Rv)uZ}UlMR+F&q zU}=lkfb0p1>1Ho){o$@}mSKIV;h*$AND7~Dl)QzpFBlSM99Kx+F7GsVK5xcR? z_4Q(Z%cgk8ST}U;;=!LwyZVu^S$>B-Waeik%wzcKTIqeX=0FP(TGQ=nxi=dsS5BYF zl@?}NT!Y!Iyos^@v7XWXA{_bV~1lxz7gC?xuXxy0_?GaN!AhRRM5>)^t%&ODd;@HN5L{MD3 zc>i2keQZVm#?NrDwbfd}_<*5^U&w0zv~n-y8=GGN-!=_`FU^cM8oVCWRFxw?BM^YD zi=Vxz4q|jwPTg+?q7_XI)-S@gQkh>w0ZUB}a{^ z_i;`Y(~fvpI!vmW*A^|P7(6+@C4UeL2WATf{P1?H5rk`5{TL zcf!CgP6Mi{MvjZS)rfo7JLDZK7M7ANd$3`{j9baD*7{#Zu-33fOYUzjvtKzR2)_T1I1s7fe&z|=)QkX;=`zX8!Byw-veM#yr;|wjO^II>!B*B z0+w%;0(=*G3V@88t!}~zx)&do(uF=073Yeh*fEhZb3Vn>t!m(9p~Y_FdV3IgR)9eT z)~e9xpI%2deTWyHlXA(7srrfc_`7ACm!R>SoIgkuF8 z!wkOhrixFy9y@)GdxAntd!!7@=L_tFD2T5OdSUO)I%yj02le`qeQ=yKq$g^h)NG;# za(0J@#VBi^5YI|QI=rq{KlxwGabZJ0dKmfWDROkcM}lUN$@DV`K7fU?8CP2H23QPi zG?YF*=Vn=kTK*#Y_{AQN&oLju|0#E=fx%YVh>S{puu&K$b;BN*jIo@VYhqPiJPzzM>#kxoy0vW9i;ne2_BIG0zyRFp<3M(iY(%*M_>q0ulV2K}Tg zkG{EWKS{i%4DUuHi%DVKy%e+Q!~Uf`>>F6NgD{{I8~nO4!VgOvtFOc7(O)X`|7n*f zxBa4CJ-v9fUUH+`7sPVvpM_C*udZ@OTGTzx56QM5y~OlrZc&w9=)B?nmd@keRn+^= zvm~4sa5987LFDnU{(N|N zJAR8H@}p1fC+H(yTI4n#%~TbImMpuqYn9cQ<0QQ%=PzZItLkC*ef9WJUvfITKWh#D zc#__8`4am9%#NslIUw+<82#SR8AYG|woLfBg#!-&dqq}@P>|I0%lbdy0lSMmNe+}o zj0zZuFr6Wb?Y{Qy-S=|r`bdrDmhnmvkRnkdn`YCleU>Q$=je}LGhh>_QAj6aa_0Oc z%Swsmui;IRx7bN*=AAS@5yW&Y2hy;3&|HAiA8}!HT6!Z!RVn~MZg`RmI6&%#tBZDx zfD+y@Z~NWlk*4l13vmt3AK2wP!fQlnBbECL>?p)F?T)<`w&QN>cP_V>r7UTcsTaaP zTOb$f!P@zf$6>890NVKbIkG8rE?9!Y97sMSZjfF?A zYR8lp`LMoz~O?iaZN;gcX;LC-%Ia*R%A&SLx!YIf29?P+=XAAojK8!^OU*@?R&DK!#G_lsn!#;S375uZ&B0HH1|BO0R90$U>qs zSvHv>H~mAgNCcjo-e+;RjY6B9NCbQrZ|BHjTkehaU<9CSkdd>Vl*ifA2LNOP&R2Qdy3k3-TQ+ zbq=#vI43x`s=%~cGyN&y4Y!FxhwgDe@i6uv8^BLL&3z*SO=D0aLjih?gY4-9uWp5or)H+v~w6n5X#F-I52z=Z_p4JB(;M| zeaVFhuR2|3UD2MzVc~^nSoD2(dD#uL_1PdnIxeA{V5n`#3xf1Zx@4lw(DsQ&H$h zw#%3O<1173hjg2_nhKi!d1ej=h7y`hVjCNB6|HTnx>SWuCE-kgTnfT+YGX4_Lun({ zDv2`>d3vrS)tTf7ps_vvh!Cx^e1BFuWnEAh0(7fkNk|-3oU|iRWdsC6U)?Raft~HN z;^$U}vZK5O8|LV$>6X5T(uYkblv{zwPxnQBh(BQ5tA~J!vGiAMYP^_ki~pkIxDfOZ zUJDwq%O~WueeV6%uN<54&u*c&E4y431cklBNrb06zGOOy4XNT~JS-q(s6@)F@ovbe ze`fial(O4(-su%6@@1+V0MsdLLMyE8;)nou(7}czU(5ASaZYDT(kUZ0L(&g$nF^n9 z9-Pi`ZZLX&)^*M6As4_2Mmc9S7OT)F8KkL2NJ)KJcnCuWU=Wy402A&45#Q9Id~BBH z0cY*xlv!uXzKrXLH!xQu(OtJvEj|0-DmRj1vjFz{c*I4$Pe(+_V|^b~S!0xm{8lq= zZv)@NlcyL3Xdz+*|L137F7y6L-2VsrKw=q^S>F6i%<{Fr8zk06$Ay-(!L$fY@7mcng!2}L0t zgi|KxfB63Xtk_Q8#ZPipQ@!zgjdpEIbK_?q17Hoi4Eiyun$hrc>T(7pOLVLQE=lgGwA+A308p& z7@=09(|$>eLy5gLe{*|3b(M;1n;C^~v?o88jYib48eR4$QGsBFzd}3QuwO^_XE(=B zq+hMi0UFC|dB{LCwch7;zYT=NK})O%sgi0k#yV;My@24^B1+CuZmYOh0^b)5Ba_)) zC%i#_Iev&nsu%I|1N5=MVc#PrlunKAs&hY|3s5;@}`>sB>}gzxuB zB=2vrRyB3uiyW(hkDUNe1@&(b`;>ZvGgw|@s{zVC#_`HXIN_^J@Etb zA7A+F?ot37T{<-vTy8h&b3e+WKHE1oh;pUQrN4yRRrx?mT_9jRa2i4l1fUnLW^Cbl z!I1>VzyFe?VELWWhM?@?t-YPZkD-Qjo@bC2(o#ZtZmr{KZsdFWItV`rs$gp{724@C zL8K5}E0+DHcWcL^{BGei4>@J-3%a#$y6;I}=upc};-NDv-z#kPX26ylOpH)Ov1uU{ zkLj6oiH6l_s+B~_z;|Jc2oi?naS7#3H63~~lWj4rUnd=fCnKdkik<@R&kch9q##G{ z4u!%=rlM~Yp3jk*t8}1B`Sv6<%Z^}~1e@aq zg|JQ`QO2pSjAm-g*?IrNc$^~sIrNBo2$m|Sxanr?Mfs>2@Auu49 zGXlsS<9XS1&8h(dD*Hl&5HBDG!^pJ*lkau_Ur+7`7z;rcs$hT4we?3bT=7Fe<>{5( z2m2(c+hUz2BTHM8dCe*Z3XX&Av;b~a=$6EF>&^E8%nyxO@m_n!q&XD^A{SRjRZQ0L~qDeC=j&0$j6=LNIz@`ni^>ch|sv}^6 zlm>?28yPl@WmDPR?Y-A9X{U9Dv_IsbXJnzKCjkRksLOg#42uG2mE_acbTQ4)J|1V>%U@K(FP3AYhL0U zdeOCPN1qLv!|#c=p!_+%VNV(GHt`RuLRV^vz<5tt-r)yOK**kUWPspVAf|}ZL{LS= z@k(@@!P&W!>wwe`x{+GrFSWhHov7hu?{KuuT%kl#WO@*WX$i_@retlhQBj++SVNCx z5$78LxP>Z=^aJ)D280r_jj=zFfMJFXCIe^B{~V@d1rl_F(qo&AB4bC-vYL>x2jSKX zpuTG-6kgp3e^T&+dtV*i6a~)v@n?n*MffN59y}<0djUX zt27R+SE#hp8bzc#;rk$jw3r4)Q@eI$*`_)=Pvge8@8|8>H3X)<9YX6cXa=ii#Le;(qKm@%0-7$>2ShnYc`j#zJ7gu_FE^?uAkL|H)UIH#gPu^40!6^J=^ zr`}iwa^!4tzW~vOMZAaKF>*8A{^8m$i(VK)>?=#l`xrVe>wseSvM_aF zATNkY>kM_P3?1kE`uIq#mvr-wuTgUH0N<&JhF=(E9%^NS*HLm!4GZ4_XI zL=R5tlG5Mk_1rPfg)sk^llFuKPMPBhuU|L5q#yP_mzxp1o&pAzi-X31sgFpIHn@($ z_>=`AB5(8tP6p2zS5VEvH5J$M` z_much3>S7t3Yo`Yx!>83-hW9LYzDKP?mKdkD#QAK8*M((sx{eBQdrR<^3ZhFP81+& zBnJMUefQyNBji~$5d88Wfw1Lv59aJN9t2!pABLg;ewJ#LXL-10;QcJl+Y4Mtngb)k6JZlCf)3uD_u)J3sYyN;NN5hNbg$%W!i-GK%e&!Us)2IExWSss$YG(hm3kJ-h%yD z>8q^n$+4I(_y_mbT{du4P%h1j3oSpjhY97{+IZ`aA4ug!vNJ6*p?<2H(2w+GD3j$I z1TUXGyNzdf>_yB3grP~FZUs<2Quw;eEi*7s(-MiIkQ%@J^+WGdQvYSUN+TRiD-xto zJ=OUU+kxGYc!HCLNbCvR4lGTp~#L;DFzGd-#gJe*xf(P3hDQz|y)?b9mwU3WUVnpcqXM<@w%r-k*Wr^gzAv)8T^sqA=Ye z!7qy&exJmAcAt~CwS#@yNmjr8*T*!A6w4~E*ibaLRs0CFo(;R3=ODhDt6zWNodmo0 zXx&bT$6&+5c>a|WJ)F4G-^GjY0H#*tY=UNyYr_q5fsrcjk(c^~e*7Lf`!Jd`)p412 zn|^*hV= zFI4UbwA%X@smDd$cQOiMC%jfitTxTb+#`9`G=2rJDfK!E=5ra|So>lc{X1$~w28i+ z4p&cTGwZ#5VueiXS9O8#;RR$yg7tL9!^)Sz&pZYIzlSh}0}V{LxL$Cu%B4U5_}k}- zm~|CsD<076x@<>m=6w6N?WaThIBP`!u{-;WF)xc=2otx*lwf|5+MkdJePjh(B z9SH+%cHGCMAXNxB{_3^otDWdsV7Ob6n{0 z+&!(;iaHOX__5z_$Qk{%xYV%Ig@7iokGBwR`3642ZP#H#v9QGbWl8<|MS*=@qO@Uj z6+SZ_v9`1paUe5tFN~v(b#J3a_Lx0+;r9giZIx-A5TxdbG>xi#AZ5_z1V}B^n)sxT zz49}eK7EWb6wR!6-qQOrHQHkUvshvq%=G2d&@(#XM*Am1;WbnJ{X_!a{ZkphD$^TQ z=Iskb&}=lBm(RHiwJoGg`*NiQ6#RB$T#LF+>#ef;Jne&MxKPX!#r`&TVEFsp2jnNx>dClzpcPy&G&13a_<0qaR3i+k212~hoQ z8nMk{JP-t04I{GW5gUBqcJW-jSMrlw}>p)ptx?WKuCUV77taMiV zHok9V=6yv+Uts@fMY&A}amC=!Yj}eL@=e%XJ#%?agkt1jWF+10{(E9mHLDa>Ll7Vj zG=3cp%ljIB-6pC}6&`xJ*6WCP|IlglLWJ^?yviI8Ve)?V_i4%n;olzny62_`-|IGi z^=}p_O>Z8M;c4|RExu70E7ePW(HWVS&E$+LL6xSQgB`QfMQJ|4pCTFowA39p5P-|$ zUtM_H2HnP8_RoS~Vwk(FhbG zH41licj%=0a;Ln2STFBvU}Ne&O&%8bYKj!h1FA#sNM`232fX|U3QPp#3C?mN2;hE9 z;)!@5ixSPl<89^7gwhHc2YAX1KJK$#*3`KOMIQ253q7-*RJ5k)zp9GBO|Ga~X*^}US5oN@aG&waHV%vi~r{t^`ptTxb zL}q1W8S7*>7oWwvgV4uFLZ(@k`R*=LO_|Gu`prs~!WQXj-NLIa^2(7IHg>BG^N zc|i{-^=&Cek9dkJFQys|sjG9i>LLz|;yCv{^1i%c*h>8zF91kLvS9HBQi~ZU!JL`B zK8N+U0fr1*6??Ium)AF!6tc1eGhXIYL6IRT7rmKp7+>?%5Pa6zC5)KY$ycF0ZJ`G5nEQDG100U-jLkH8^UE4g6wq?sg%pP=-$&G#bcN`^?w3a6 z((s$6eRKcSEIslW-kk5Qi|5Mg-(xdLF}PxxVh$PuO}#aR6pW1kV4Af!Bqh*btXNNZ z>-4(IUl+L4dw+3LcpGut=qB45O+W)Q5?*zZ2A6rJcg`qkSvWA!j^r2mqKuCm6`Py? z@^T#Ux04HemPGd!Hs7NkZdVn1}8_j`o?)*OKZGS!`ff)gF zG?v-lj$wWNWCcw2Mg2o18D~1?3_b0XzdiKBNkYSDpcv@&kp0POmweJE2ZkIQ3B!a! zIgIoE+Xv?;34kyo^QYjZk+tEqZvq^#QG(OzX4~X+KtsoQoddTWUR(yo8R+ObEF1j<-syWOb>)JQ&Zbdu(sctU%Mt zW&YR0{ttY2TTXYZ?~WNU&cES1Z2q(7SrWDh``!J(JM+Nk$!hu&Y;(7E`ZNKTe0w+% zJc?Qnw2B+%UR}0;cB0Rufa(7-3FF}?629@LgTiEC&2uyL6NxexOp?AKT^aAx3gi(W zao>r>MPw0eQ3>IV02uLsC@>yK_epX6GRg4{NEL2wPPF9=*L2RV3yyK8DhuEK>rmmV z`&Q~#c`lgR&93TdOCja|ewOXmPNRh7!&dMT(1ett#iDr8HZW~VqWW@7fe9B6;7S+? zbC`d4@MEau&mKlOPKd>*10q0c{~^baw6!a*w^sY#0Xim{oOsiXiDOhbG&kl3c$$n1 zMRrD83&QucDSEcV*7LIp8VTA@F<%qe+_c`L;6on(>SjAU^}5c9!BCffT>$VQhe=)z z8(=Ej{5>jhmjB3{xDfj2R@VmHQ!CqjlO4KnuOmvHy3K#po$yp_V;p_MKjh1`(rzj6 zHW956k1yvntz{_g?Xbs`avK(IjlTnsu%htO;D7 z?J#x^EzuvVn&NA=!MEj7cwe5A-Z$Zk2LBZH$~%E* zf`((xH0?`}hs|HA%mtwfOEsZJxxrennkTYcwP#FKO5%Lpc^JXhSpV|ZH$Wr;`}`_( zIP==gd3LYyVtwD|*ZJGi{7~x8{=^bGVqu0RJ`n_BZH9+}kz%-4ZRsImi@rx%=ZEKs zcPnUXo6hbJV>fH;@1|bAHIe0ijYI*&kdT|HkDS$9No9 zCHo=*HWb~U+Dtzxr+Esao}6@|;Pf+E$ay0$kQp#s{wlw+7aIKbMdf`OqhoG*;Tco0 zjrP}VQG#Y2cJuqoJg&5({)S(BA}q9T1lGeWRyu=Je|)I!6a+aj!IP^1({)ZYe&x6w zt3a)Dq^TB+A7CdB0-}#z2Ur$W&h3YVw8==!xONy$uQmDWh-@15iEOt!q2m&?ZLA|w z8loSb(0}7y6Xu0?M5Uf4>VZGluB`wMf2oh;m)ghxVda>3m}4%V)r^0nVQ5V6f3>*) z0&VN!N0~GC^P}vj$`EDMZEmVV;N&RISY2C;$0;2(<{Lt&PKzqRByQdiEHGAbwtbS zPj`Da5%U6k1oEtVzI}QNw;!hT6F+~|@=c@$C4NtO@=xgP?|5MyZAyuCzcvq4rdAv@C06%gZ`9%I);R6UGiGJobfux+<0DLS&|MSG4UH z_~o{^^9>ixMg~mY!-@Fai{xaE4^;qy9iZN15Gbn5ZqHWf>Jc5Rv6(#n8`1NcCsdmG zab*dSXVPaE?)wCalD;$ivF%@nB#7D`@YG04p6ed9m}4iJW|pfVMLE<-c{=-8$e?cH zUdU#mCj4gb zZKA^b9p*9S(}8@tw~1RNPHr7tQr;P+-)D8|sq=*o)G%RGqt> zzP5yf`pVxb)I51D_G~Xp^GNK zVI6sAX)a9s)e{8N3?35YA6aQTXuyszK3ah~CemzA&CII#8F&F#KN41~8I^&_%}6MCNb{W87qAF`zj_Y^szhb> z3p3}KbOxotY|(lD=;)`fYE_*{S}x;f^SW#)SU&5X#o|-R|trpa|L5PS5aa0 zTHw8%SDSVtU4?vyrhnq+^@dgFS)|(y{~(4j%3UEiO-rBM9%`)8(dh33pMLiuurNY# z#10AsQ7%*0Cu_DSAU}P;X(JwA64~Q_^R%d_zSm^6Aux?Pn70PM>9EvLeOX z&w9c)pGmcL22;MO3C_B>=NC0RJpMp8?#ZUf=GWRvy z6RHq3B}=MGVg?9@iKFBpsvnkVh3{Vpp=`CcD=u~@ql{my|6?3ssi3mCOPnjI&E}VC zc@X+Yl>;;DNo0W0`0th!X{?luDhOC{E8N=?!w}K1{V=)+1={m(f`Oc|N=07>}3;z{-(A zm{JL=j?Sro5iecmE2-pWlRf(r%|HEQ7kgwQ9+kt=NBhtQI7OwcZ#3%$Uf%^r2nhjY zoQ08MfC%_X{O9~WcirMZMhn#z^ux4Erx-tf-6bHD)9eH&^L>^jvAd^9A^DCDs?0;k zkm7LE*KjP6`2d17MrQaaLqd_Rka}J$csvUec#hw78<=s(hyR>065~YCVCA9+#Q+; za(*L0IEw!r5P|@-;x33L$Lv9 zcuN8YG&g{<(SeJG18~(b!5yywSqQiLAX0;---;}mF5&b4lg|T?LwKREa{9YX_-zL@ZE?Zqi@HxK^2KO1>0LATu{te=T zprmHtY)bDVfxI1S}KBE7V zznP7KQ8HekWU#W6mw`dr-boV}pMQR==&5=Q5T=_q091jfc;R*jX#&=MQ%~@E@9^?`$v48ks<>(fI(F6L(5ppKy|$HWng*bKOb(4|cMUB&z$#ob#XV z5-mg)gmFIybZf=znm3ZPyUO^GJfxt0kmHjaTZ|sthsxXw&}Y)fOUSg=JhRSR^UjZ- zhqqb}Wsyw4zdnj6@#BAJa#-PdI4_dgafFXh85DsEQ_cT+5)XpZq$fZlBA_9UsE9r6 zEFec5?uqN@QhJ^IzwZrwl-5J`CmVPv{(YDTqEqWR^dI;5hXc~cxP%B3v&~s0`Ct89 z@S`i~a^c%V^N81dDT*ItFS*&IN;@O$EgzX0e7x&}TD=!zS}hTpezBLS>mdX(5< z)8DEI(-o_D)c-UX@dA1MuJ*yc>Hf4|`*B2S_O>w*-tbUwtiu`;W(Ud{HTty@(&x(T(F&;M zJ=?H>6`B7nf-90e8V`WSVp|0oEKB-P2M{}4ZDawzvM&a!y>`Y#jCsD%T_l``@ah(I2nJs~Q|%uSKu@k!m~*8B*IoA{*TgtF<(5sHCGG;n@NE%~Xt(G$^&<87u;}Na zx-8cq0g`uA(&RBFo=-4Y1GUZ<``Zw{xL4jfHkZw~%~wvtGueszcXt)_QwH8g!; z%s&3kSa~R$dO$-%L-)c@_hi7&>{6L_M>OZFkUQu;{sL_bUMStNrt{{&O(Wn~*zPOk zB>dnfszb29NSTf2pqIs68k|p-UrSrxgLHqi?3N-UFa!LHy9n1)=s>`yS+J{MEzS@ zNlfGtpma7kG&LR3JE@wB%rFA*h~~KitlO=IP)ZjN6dQLM6qsry zHkB#cyNh#n`)}bCrN1My*;k)^@>e4gJ`LJK?2)Pwp?4Tl4)4FA0(tvY+#1jOUM)xw zlMz4x-f@g^+yKUN`?Vu)|AwujArnM~Pa@y*Q9S8eS(u{-S%(Z5=R~pRl5ZGDjdqH% zC8rW&{##wOpU_oTIG4WXMk4&%2t1;lWcW5&!yxmOT*!hBcKyTqEcNoO+R2;Q?Yj+W z1-Y4?59fijz4(MIDwGe4-baYf08UCs;r|YefD-Md2ST;=cxwpgW=tR76-dQVAhn^= zG9Wk5lQk%jIR@KNU!UMp6@BfU;r+;y4VQ)D2!Il9HX%yW-9nOzV+m$YKzVaO`B8S7t z$!S2Mz`xw>V(RjE`0>bQp<0y&h~Y=M#jpy!#=dE>`=e_AjSZq6u!Dy1xJf~-7|0F! zPR9|n`e_7D2DIV2H(CESQ}hA>U>n|6`%z?YKEA~)BOVY%y=jPV zT=44R!L?J)736X#csn|lfBJ)o8ixaZclguWgrGO<`TN2FMfO}7;5}d+BlK0yTSH3* z4!=;5rOh85&2|x=46hkNaz?)U8&=bcfh=N_#8BNpZ2v$aVBo;sk^*X`v;4-LU;D>! zM*h12MxXIQy)SfAqE4;jY)wgnppazZkdNNVVF;(PLf^qK$FgY9+VFyBKE7UC|f z`R|?&egV11K3s$rJ6!GvoeW=jV*!-e(wA;x(2=d0E_e_%0x--0o8#~m^H1%AH5Z^B zn!TNPn927*bvaf0pt}zhK0o^V@WlGwwKo(*nQ|Q~4_;>~-8y20`HP>@UJa)3nEnGG z5Hwhs|FcmFG16ZVNb5hL`2Gc1{zWIMM{_OiKewV!hCi}U!VuE?s9wU-QbZ!)+Y^tS zGzp5OSi5iq6hmEr$w}&9DFgoB+i*`q`8TBi^MVS{SKEb8Aw%@K7@XCo(De2A`6%mf&a2#~y1N)+kJLD$1HCP!22)(U}xo2|j?WRzt(11j8Z_*v;P$R+Ug*Gy3VxV4K; zGGUGabnW*`Z}~`ydXL-l9e=GC$pY#z|63vy>E*m=$=j}iWP{sRTh0%H54`t>2xYH% zsk+M&u&pNgMCM@3e)Xc?jBWX-TIR_cQ1Z!RW7!B zBjZX=+^3}?SE)B+$EP+0oi1Fp5blDT?*}nsP>filqXH{ms zxU<$hetC`u)Wi+x|EKL-`y^#aQX+sDYIa{M;V%LqLrOk~lR>u0Q!+pyQSU4zY`?E^ z|5@)C)w6G_=i5YYC5SE_u(7hDNYr}uKT|@DSqF%S++lTIbIk^$a>{~0IH8KNFEy%+ zW#$&!ynpgNJh>6uR~?2c)ZMW+h0OKu231(7L_vETPaR+(P)Zy%0~yGm>E9?@@x!Jy z3PYgS}Q@b}x}E#F27@F+j}0=&Ql4gES&f8acMrPAVlVs9$97`FR))R5wI zc&}KFI1UIewh>3PkhnB7u zS3AT8_*|nexznG|Z*DU0c!K@jsI4J)5#DyNi#|e#`l1Vv1`1)*NVcy0LZ``aL0n8B zecupJ(rhq3u8bW0NIRhKYq$v1li+jp*4hfAd&wxYDE8vn1TQ7S@bTM|I2Ob z8vMOIxA7&_j{AKmD+O@EyXT`|dElt0pED^@IV0m)RPBUs*5jW60>>w1!@_G3aBKzG z_f(KfAPBk}-jQtR*Sroq!*3rbQ_m27e+YdzQjUb<_*k8vc_C)y!@cj5E>NxUhPu&g z@Z2<~esU`)ih+4opWe+K7sbN9n*9@n>#@n3*o z?xoROgDuvhq>jJ;Ve{6i<3roQNfgo5^4Q4(|GNExO2Dr7GjgA2zWuKp_K)K0R(6lv z!l$!zW-+T6mb3gQaAFviTQi{|*t%>{(mhTdy+y;Re4qT@kccy#{b z&zWy~kLO@>*WPj2k#H)|7L&gAJ37DmHQAme#@m;(Y8Nu^`D5vf8sZFW#+lA2!HK=( zJ)#hO6JD*`o~&c*&46d}g=Qj@SsoB5ikC z^1V8E+&<-OzuS_C`p5<<(A6fB`LXT(!kV^0_~hL6PpW4={l%|#xgdh?5EIk~lu8{D z2hiyhv3Yxij_#$Wu>P@7SYsl`-~3;}Ktx{34_NL^Kwin&=?!HDv3elQDbcU*qyYpN z(#yw~f1vFGK-t%CC-qa-4FYHbA^h>bag-I&*qaxwn?Qv|idE$<>1H|Gr6JtUu(he2$eg!N z@HTF@dG1)*y;4fxe)4_ZkpaBHH9hXp9p4|gLrRQyuevRd@gSS}JhRnWqrvm|U@>qM z=yl7RQROTKwQtzP3!zUF)_6Ld#NGA6v~2{J9Dd`h6{%+XsU#qGLh%`fB1Hc?wfayK zN`H4BpDp)npVQuu$DVW1qsBS&AJ2eP%6Qw>;k{)Z$8%HL=Q4(a$Ng2_vHw&vA!1L+9zc8vaX2GtqJ{L-;gvF0IR$em zMQ8@{Qp3+3Quk)TJ$?I<8KmwzD*7#(q<@Mc`dchngW}cRG14(Z6K7{T|LhFXwhqUQ;BET;cYqPcAcMgt6M$V9$(?jHo@Sud$an$U&5F zZ1QNh^ztt)E*d#Ij;<43oSKKnd+WNr$_r}+s_O_x6DZSB10*5Q{ourqq>mTl| zx4y^(cy+9;t@R=*j>3_dmm_m)$k$#937V(sllby&5)Xex^UD-|m|q<(jEd#@DV(of zAd7sSdmS*zUDqJ9|K%O2J2OfdUiK{{b{PCy)pi<;hp~7v1CQj&4-10 zgO<3dqhYH1#-Fa}Q{pjql5>>P6gZH21zLfxZ4$SK4T@7b!|`nWF9b*84Bq8&Eht;9 z*P72x&NUCZ7*@B$`FtE=hz5b}S`|c6Ey+j@D1ZibjJaRlR;{cxAWv z?Nqa>QqV*H-*zzaPvpLMHt~nl(x6?vrPpR?zn7~wow?oj*1TKmx4j71>$hvtC$DLD zUrz0^tiP0792U&dxJxNv@r}Elsjn^aSLUu=9#mD{&9n8|ayIL$!H3s>%KEvbchBFW z%cd?VU83mGF#Dar9*s~w&AnmQRQIOvR+uWsuZ?+|a=TzApXO@q^(r%8=}iv#wCnFq z=K9}JbqU@k99Q%j-}NNk+qLCP)jXfmOO|)@?mHcnynd6({mJisP1_}u7k)|eYHXWK z63eQ)E$ufFi!3CWUY2gw%e>omCv}qEX66aH-k&35f9`Q@Us|NPetVqe8=dX*VxJdn ze`q7b=Dn(UA(2sf&g)cOmQFhNJ#<-aMELJZbA#@to>25@kbW<)&!X01 z%NMJt>1ST)tyX)h@?`DxhbgCHr>S4wv}WC&Nw-!{+Z7$2D}74QAcXTvip=M0%Tp_N zor=k`)t|ra^ySr-+(|R9mB(E=`MX#y(wSw)$!iymzB;^c*>%&^*7HxTnRga=soSZT zdDl+9s;r!v8hk6POtzBaig4pRp7eWF(<8gufvNHPu6xs-=e{;mnHzJyGKE+8L0j}; z@%8-e^UCL5HhMiR>sD3Rve&yVZ#{Q1*CO8c+qSr^Z#CN;)(X5>tGG5yUw3<+CfhaL z%bP;hZ?jvgJU67BWyiy74_)6r)_nSxttxn0`0?HE^5(uydHVgP+HE$V?Lv)Leti43 zWA|;f-RqX``95>)^P-fw!Vi{3KNsII-*5f){gdxqd%gVdB1sOBNe=nEW%;i~g_P8J w!5uhoe-Jcg1nPN%MiEAtgE$;km@@t6ukO)1^!cY^83Pb_y85}Sb4q9e0FIsP9{>OV diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png deleted file mode 100644 index 2f1632cfddf3d9dade342351e627a0a75609fb46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2218 zcmV;b2vzrqP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuE6iGxuRCodHTWf3-RTMruyW6Fu zQYeUM04eX6D5c0FCjKKPrco1(K`<0SL=crI{PC3-^hZU0kQie$gh-5!7z6SH6Q0J% zqot*`H1q{R5fHFYS}dje@;kG=v$L0(yY0?wY2%*c?A&{2?!D*x?m71{of2gv!$5|C z3>qG_BW}7K_yUcT3A5C6QD<+{aq?x;MAUyAiJn#Jv8_zZtQ{P zTRzbL3U9!qVuZzS$xKU10KiW~Bgdcv1-!uAhQxf3a7q+dU6lj?yoO4Lq4TUN4}h{N z*fIM=SS8|C2$(T>w$`t@3Tka!(r!7W`x z-isCVgQD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5^Dc=k7F7?nm3nIw$NVmU-+R>> zyqOR$-2SDpJ}Pt;^RkJytDVXNTsu|mI1`~G7yw`EJR?VkGfNdqK9^^8P`JdtTV&tX4CNcV4 z&N06nZa??Fw1AgQOUSE2AmPE@WO(Fvo`%m`cDgiv(fAeRA%3AGXUbsGw{7Q`cY;1BI#ac3iN$$Hw z0LT0;xc%=q)me?Y*$xI@GRAw?+}>=9D+KTk??-HJ4=A>`V&vKFS75@MKdSF1JTq{S zc1!^8?YA|t+uKigaq!sT;Z!&0F2=k7F0PIU;F$leJLaw2UI6FL^w}OG&!;+b%ya1c z1n+6-inU<0VM-Y_s5iTElq)ThyF?StVcebpGI znw#+zLx2@ah{$_2jn+@}(zJZ{+}_N9BM;z)0yr|gF-4=Iyu@hI*Lk=-A8f#bAzc9f z`Kd6K--x@t04swJVC3JK1cHY-Hq+=|PN-VO;?^_C#;coU6TDP7Bt`;{JTG;!+jj(` zw5cLQ-(Cz-Tlb`A^w7|R56Ce;Wmr0)$KWOUZ6ai0PhzPeHwdl0H(etP zUV`va_i0s-4#DkNM8lUlqI7>YQLf)(lz9Q3Uw`)nc(z3{m5ZE77Ul$V%m)E}3&8L0 z-XaU|eB~Is08eORPk;=<>!1w)Kf}FOVS2l&9~A+@R#koFJ$Czd%Y(ENTV&A~U(IPI z;UY+gf+&6ioZ=roly<0Yst8ck>(M=S?B-ys3mLdM&)ex!hbt+ol|T6CTS+Sc0jv(& z7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBIo*981BU%%U z@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M2 z@O~lQ0OiKQp}o9I;?uxCgYVV?FH|?Riri*U$Zi_`V2eiA>l zdSm6;SEm6#T+SpcE8Ro_f2AwxzI z44hfe^WE3!h@W3RDyA_H440cpmYkv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2kVYr - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/AppInfo.xcconfig b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/AppInfo.xcconfig deleted file mode 100644 index 679d82fe5..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/AppInfo.xcconfig +++ /dev/null @@ -1,14 +0,0 @@ -// Application-level settings for the Runner target. -// -// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the -// future. If not, the values below would default to using the project name when this becomes a -// 'flutter create' template. - -// The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = quill_native_bridge_example - -// The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = dev.flutterquill.quillNativeBridgeExample - -// The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright Š 2024 dev.flutterquill. All rights reserved. diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Debug.xcconfig b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Debug.xcconfig deleted file mode 100644 index 36b0fd946..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Debug.xcconfig" -#include "Warnings.xcconfig" diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Release.xcconfig b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Release.xcconfig deleted file mode 100644 index dff4f4956..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Release.xcconfig" -#include "Warnings.xcconfig" diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Warnings.xcconfig b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Warnings.xcconfig deleted file mode 100644 index 42bcbf478..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Configs/Warnings.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings -GCC_WARN_UNDECLARED_SELECTOR = YES -CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES -CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE -CLANG_WARN__DUPLICATE_METHOD_MATCH = YES -CLANG_WARN_PRAGMA_PACK = YES -CLANG_WARN_STRICT_PROTOTYPES = YES -CLANG_WARN_COMMA = YES -GCC_WARN_STRICT_SELECTOR_MATCH = YES -CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES -CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES -GCC_WARN_SHADOW = YES -CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/DebugProfile.entitlements b/quill_native_bridge/quill_native_bridge/example/macos/Runner/DebugProfile.entitlements deleted file mode 100644 index dddb8a30c..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner/DebugProfile.entitlements +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.network.server - - - diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Info.plist b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Info.plist deleted file mode 100644 index 4789daa6a..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Info.plist +++ /dev/null @@ -1,32 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - $(PRODUCT_COPYRIGHT) - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/MainFlutterWindow.swift b/quill_native_bridge/quill_native_bridge/example/macos/Runner/MainFlutterWindow.swift deleted file mode 100644 index 3cc05eb23..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner/MainFlutterWindow.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Cocoa -import FlutterMacOS - -class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) - - RegisterGeneratedPlugins(registry: flutterViewController) - - super.awakeFromNib() - } -} diff --git a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Release.entitlements b/quill_native_bridge/quill_native_bridge/example/macos/Runner/Release.entitlements deleted file mode 100644 index 852fa1a47..000000000 --- a/quill_native_bridge/quill_native_bridge/example/macos/Runner/Release.entitlements +++ /dev/null @@ -1,8 +0,0 @@ - - - - - com.apple.security.app-sandbox - - - diff --git a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml b/quill_native_bridge/quill_native_bridge/example/pubspec.yaml deleted file mode 100644 index 569249daa..000000000 --- a/quill_native_bridge/quill_native_bridge/example/pubspec.yaml +++ /dev/null @@ -1,45 +0,0 @@ -name: quill_native_bridge_example -description: "Demonstrates usage of the quill_native_bridge plugin." -version: 1.0.0+1 -publish_to: 'none' - -environment: - sdk: ^3.5.2 -dependencies: - flutter: - sdk: flutter - - quill_native_bridge: ^10.7.4 - -dependency_overrides: - quill_native_bridge: - path: ../ - quill_native_bridge_platform_interface: - path: ../../quill_native_bridge_platform_interface - quill_native_bridge_android: - path: ../../quill_native_bridge_android - quill_native_bridge_web: - path: ../../quill_native_bridge_web - quill_native_bridge_windows: - path: ../../quill_native_bridge_windows - quill_native_bridge_linux: - path: ../../quill_native_bridge_linux - quill_native_bridge_ios: - path: ../../quill_native_bridge_ios - quill_native_bridge_macos: - path: ../../quill_native_bridge_macos - -dev_dependencies: - integration_test: - sdk: flutter - flutter_test: - sdk: flutter - - flutter_lints: ^4.0.0 - - image_compare: ^1.1.2 - -flutter: - uses-material-design: true - assets: - - assets/ \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/example/web/favicon.png b/quill_native_bridge/quill_native_bridge/example/web/favicon.png deleted file mode 100644 index 8aaa46ac1ae21512746f852a42ba87e4165dfdd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM diff --git a/quill_native_bridge/quill_native_bridge/example/web/icons/Icon-192.png b/quill_native_bridge/quill_native_bridge/example/web/icons/Icon-192.png deleted file mode 100644 index b749bfef07473333cf1dd31e9eed89862a5d52aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5292 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 diff --git a/quill_native_bridge/quill_native_bridge/example/web/icons/Icon-512.png b/quill_native_bridge/quill_native_bridge/example/web/icons/Icon-512.png deleted file mode 100644 index 88cfd48dff1169879ba46840804b412fe02fefd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8252 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 diff --git a/quill_native_bridge/quill_native_bridge/example/web/icons/Icon-maskable-192.png b/quill_native_bridge/quill_native_bridge/example/web/icons/Icon-maskable-192.png deleted file mode 100644 index eb9b4d76e525556d5d89141648c724331630325d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! diff --git a/quill_native_bridge/quill_native_bridge/example/web/icons/Icon-maskable-512.png b/quill_native_bridge/quill_native_bridge/example/web/icons/Icon-maskable-512.png deleted file mode 100644 index d69c56691fbdb0b7efa65097c7cc1edac12a6d3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20998 zcmeFZ_gj-)&^4Nb2tlbLMU<{!p(#yjqEe+=0IA_oih%ScH9@5#MNp&}Y#;;(h=A0@ zh7{>lT2MkSQ344eAvrhici!td|HJuyvJm#Y_w1Q9Yu3!26dNlO-oxUDK_C#XnW^Co z5C{VN6#{~B0)K2j7}*1Xq(Nqemv23A-6&=ZpEijkVnSwVGqLv40?n0=p;k3-U5e5+ z+z3>aS`u9DS=!wg8ROu?X4TFoW6CFLL&{GzoVT)ldhLekLM|+j3tIxRd|*5=c{=s&*vfPdBr(Fyj(v@%eQj1Soy7m4^@VRl1~@-PV7y+c!xz$8436WBn$t{=}mEdK#k`aystimGgI{(IBx$!pAwFoE9Y`^t^;> zKAD)C(Dl^s%`?q5$P|fZf8Xymrtu^Pv(7D`rn>Z-w$Ahs!z9!94WNVxrJuXfHAaxg zC6s@|Z1$7R$(!#t%Jb{{s6(Y?NoQXDYq)!}X@jKPhe`{9KQ@sAU8y-5`xt?S9$jKH zoi}6m5PcG*^{kjvt+kwPpyQzVg4o)a>;LK`aaN2x4@itBD3Aq?yWTM20VRn1rrd+2 zKO=P0rMjEGq_UqpMa`~7B|p?xAN1SCoCp}QxAv8O`jLJ5CVh@umR%c%i^)6!o+~`F zaalSTQcl5iwOLC&H)efzd{8(88mo`GI(56T<(&p7>Qd^;R1hn1Y~jN~tApaL8>##U zd65bo8)79CplWxr#z4!6HvLz&N7_5AN#x;kLG?zQ(#p|lj<8VUlKY=Aw!ATqeL-VG z42gA!^cMNPj>(`ZMEbCrnkg*QTsn*u(nQPWI9pA{MQ=IsPTzd7q5E#7+z>Ch=fx$~ z;J|?(5jTo5UWGvsJa(Sx0?S#56+8SD!I^tftyeh_{5_31l6&Hywtn`bbqYDqGZXI( zCG7hBgvksX2ak8+)hB4jnxlO@A32C_RM&g&qDSb~3kM&)@A_j1*oTO@nicGUyv+%^ z=vB)4(q!ykzT==Z)3*3{atJ5}2PV*?Uw+HhN&+RvKvZL3p9E?gHjv{6zM!A|z|UHK z-r6jeLxbGn0D@q5aBzlco|nG2tr}N@m;CJX(4#Cn&p&sLKwzLFx1A5izu?X_X4x8r@K*d~7>t1~ zDW1Mv5O&WOxbzFC`DQ6yNJ(^u9vJdj$fl2dq`!Yba_0^vQHXV)vqv1gssZYzBct!j zHr9>ydtM8wIs}HI4=E}qAkv|BPWzh3^_yLH(|kdb?x56^BlDC)diWyPd*|f!`^12_U>TD^^94OCN0lVv~Sgvs94ecpE^}VY$w`qr_>Ue zTfH~;C<3H<0dS5Rkf_f@1x$Gms}gK#&k()IC0zb^QbR!YLoll)c$Agfi6MKI0dP_L z=Uou&u~~^2onea2%XZ@>`0x^L8CK6=I{ge;|HXMj)-@o~h&O{CuuwBX8pVqjJ*o}5 z#8&oF_p=uSo~8vn?R0!AMWvcbZmsrj{ZswRt(aEdbi~;HeVqIe)-6*1L%5u$Gbs}| zjFh?KL&U(rC2izSGtwP5FnsR@6$-1toz?RvLD^k~h9NfZgzHE7m!!7s6(;)RKo2z} zB$Ci@h({l?arO+vF;s35h=|WpefaOtKVx>l399}EsX@Oe3>>4MPy%h&^3N_`UTAHJ zI$u(|TYC~E4)|JwkWW3F!Tib=NzjHs5ii2uj0^m|Qlh-2VnB#+X~RZ|`SA*}}&8j9IDv?F;(Y^1=Z0?wWz;ikB zewU>MAXDi~O7a~?jx1x=&8GcR-fTp>{2Q`7#BE#N6D@FCp`?ht-<1|y(NArxE_WIu zP+GuG=Qq>SHWtS2M>34xwEw^uvo4|9)4s|Ac=ud?nHQ>ax@LvBqusFcjH0}{T3ZPQ zLO1l<@B_d-(IS682}5KA&qT1+{3jxKolW+1zL4inqBS-D>BohA!K5++41tM@ z@xe<-qz27}LnV#5lk&iC40M||JRmZ*A##K3+!j93eouU8@q-`W0r%7N`V$cR&JV;iX(@cS{#*5Q>~4BEDA)EikLSP@>Oo&Bt1Z~&0d5)COI%3$cLB_M?dK# z{yv2OqW!al-#AEs&QFd;WL5zCcp)JmCKJEdNsJlL9K@MnPegK23?G|O%v`@N{rIRa zi^7a}WBCD77@VQ-z_v{ZdRsWYrYgC$<^gRQwMCi6);%R~uIi31OMS}=gUTE(GKmCI z$zM>mytL{uNN+a&S38^ez(UT=iSw=l2f+a4)DyCA1Cs_N-r?Q@$3KTYosY!;pzQ0k zzh1G|kWCJjc(oZVBji@kN%)UBw(s{KaYGy=i{g3{)Z+&H8t2`^IuLLKWT6lL<-C(! zSF9K4xd-|VO;4}$s?Z7J_dYqD#Mt)WCDnsR{Kpjq275uUq6`v0y*!PHyS(}Zmv)_{>Vose9-$h8P0|y;YG)Bo}$(3Z%+Gs0RBmFiW!^5tBmDK-g zfe5%B*27ib+7|A*Fx5e)2%kIxh7xWoc3pZcXS2zik!63lAG1;sC1ja>BqH7D zODdi5lKW$$AFvxgC-l-)!c+9@YMC7a`w?G(P#MeEQ5xID#<}W$3bSmJ`8V*x2^3qz zVe<^^_8GHqYGF$nIQm0Xq2kAgYtm#UC1A(=&85w;rmg#v906 zT;RyMgbMpYOmS&S9c38^40oUp?!}#_84`aEVw;T;r%gTZkWeU;;FwM@0y0adt{-OK z(vGnPSlR=Nv2OUN!2=xazlnHPM9EWxXg2EKf0kI{iQb#FoP>xCB<)QY>OAM$Dcdbm zU6dU|%Mo(~avBYSjRc13@|s>axhrPl@Sr81{RSZUdz4(=|82XEbV*JAX6Lfbgqgz584lYgi0 z2-E{0XCVON$wHfvaLs;=dqhQJ&6aLn$D#0i(FkAVrXG9LGm3pSTf&f~RQb6|1_;W> z?n-;&hrq*~L=(;u#jS`*Yvh@3hU-33y_Kv1nxqrsf>pHVF&|OKkoC)4DWK%I!yq?P z=vXo8*_1iEWo8xCa{HJ4tzxOmqS0&$q+>LroMKI*V-rxhOc%3Y!)Y|N6p4PLE>Yek>Y(^KRECg8<|%g*nQib_Yc#A5q8Io z6Ig&V>k|~>B6KE%h4reAo*DfOH)_01tE0nWOxX0*YTJgyw7moaI^7gW*WBAeiLbD?FV9GSB zPv3`SX*^GRBM;zledO`!EbdBO_J@fEy)B{-XUTVQv}Qf~PSDpK9+@I`7G7|>Dgbbu z_7sX9%spVo$%qwRwgzq7!_N;#Td08m5HV#?^dF-EV1o)Q=Oa+rs2xH#g;ykLbwtCh znUnA^dW!XjspJ;otq$yV@I^s9Up(5k7rqhQd@OLMyyxVLj_+$#Vc*}Usevp^I(^vH zmDgHc0VMme|K&X?9&lkN{yq_(If)O`oUPW8X}1R5pSVBpfJe0t{sPA(F#`eONTh_) zxeLqHMfJX#?P(@6w4CqRE@Eiza; z;^5)Kk=^5)KDvd9Q<`=sJU8rjjxPmtWMTmzcH={o$U)j=QBuHarp?=}c??!`3d=H$nrJMyr3L-& zA#m?t(NqLM?I3mGgWA_C+0}BWy3-Gj7bR+d+U?n*mN$%5P`ugrB{PeV>jDUn;eVc- zzeMB1mI4?fVJatrNyq|+zn=!AiN~<}eoM#4uSx^K?Iw>P2*r=k`$<3kT00BE_1c(02MRz4(Hq`L^M&xt!pV2 zn+#U3@j~PUR>xIy+P>51iPayk-mqIK_5rlQMSe5&tDkKJk_$i(X&;K(11YGpEc-K= zq4Ln%^j>Zi_+Ae9eYEq_<`D+ddb8_aY!N;)(&EHFAk@Ekg&41ABmOXfWTo)Z&KotA zh*jgDGFYQ^y=m)<_LCWB+v48DTJw*5dwMm_YP0*_{@HANValf?kV-Ic3xsC}#x2h8 z`q5}d8IRmqWk%gR)s~M}(Qas5+`np^jW^oEd-pzERRPMXj$kS17g?H#4^trtKtq;C?;c ztd|%|WP2w2Nzg@)^V}!Gv++QF2!@FP9~DFVISRW6S?eP{H;;8EH;{>X_}NGj^0cg@ z!2@A>-CTcoN02^r6@c~^QUa={0xwK0v4i-tQ9wQq^=q*-{;zJ{Qe%7Qd!&X2>rV@4 z&wznCz*63_vw4>ZF8~%QCM?=vfzW0r_4O^>UA@otm_!N%mH)!ERy&b!n3*E*@?9d^ zu}s^By@FAhG(%?xgJMuMzuJw2&@$-oK>n z=UF}rt%vuaP9fzIFCYN-1&b#r^Cl6RDFIWsEsM|ROf`E?O(cy{BPO2Ie~kT+^kI^i zp>Kbc@C?}3vy-$ZFVX#-cx)Xj&G^ibX{pWggtr(%^?HeQL@Z( zM-430g<{>vT*)jK4aY9(a{lSy{8vxLbP~n1MXwM527ne#SHCC^F_2@o`>c>>KCq9c(4c$VSyMl*y3Nq1s+!DF| z^?d9PipQN(mw^j~{wJ^VOXDCaL$UtwwTpyv8IAwGOg<|NSghkAR1GSNLZ1JwdGJYm zP}t<=5=sNNUEjc=g(y)1n5)ynX(_$1-uGuDR*6Y^Wgg(LT)Jp><5X|}bt z_qMa&QP?l_n+iVS>v%s2Li_;AIeC=Ca^v1jX4*gvB$?H?2%ndnqOaK5-J%7a} zIF{qYa&NfVY}(fmS0OmXA70{znljBOiv5Yod!vFU{D~*3B3Ka{P8?^ zfhlF6o7aNT$qi8(w<}OPw5fqA7HUje*r*Oa(YV%*l0|9FP9KW@U&{VSW{&b0?@y)M zs%4k1Ax;TGYuZ9l;vP5@?3oQsp3)rjBeBvQQ>^B;z5pc=(yHhHtq6|0m(h4envn_j787fizY@V`o(!SSyE7vlMT zbo=Z1c=atz*G!kwzGB;*uPL$Ei|EbZLh8o+1BUMOpnU(uX&OG1MV@|!&HOOeU#t^x zr9=w2ow!SsTuJWT7%Wmt14U_M*3XiWBWHxqCVZI0_g0`}*^&yEG9RK9fHK8e+S^m? zfCNn$JTswUVbiC#>|=wS{t>-MI1aYPLtzO5y|LJ9nm>L6*wpr_m!)A2Fb1RceX&*|5|MwrvOk4+!0p99B9AgP*9D{Yt|x=X}O% zgIG$MrTB=n-!q%ROT|SzH#A$Xm;|ym)0>1KR}Yl0hr-KO&qMrV+0Ej3d@?FcgZ+B3 ztEk16g#2)@x=(ko8k7^Tq$*5pfZHC@O@}`SmzT1(V@x&NkZNM2F#Q-Go7-uf_zKC( zB(lHZ=3@dHaCOf6C!6i8rDL%~XM@rVTJbZL09?ht@r^Z_6x}}atLjvH^4Vk#Ibf(^LiBJFqorm?A=lE zzFmwvp4bT@Nv2V>YQT92X;t9<2s|Ru5#w?wCvlhcHLcsq0TaFLKy(?nzezJ>CECqj zggrI~Hd4LudM(m{L@ezfnpELsRFVFw>fx;CqZtie`$BXRn#Ns%AdoE$-Pf~{9A8rV zf7FbgpKmVzmvn-z(g+&+-ID=v`;6=)itq8oM*+Uz**SMm_{%eP_c0{<%1JGiZS19o z@Gj7$Se~0lsu}w!%;L%~mIAO;AY-2i`9A*ZfFs=X!LTd6nWOZ7BZH2M{l2*I>Xu)0 z`<=;ObglnXcVk!T>e$H?El}ra0WmPZ$YAN0#$?|1v26^(quQre8;k20*dpd4N{i=b zuN=y}_ew9SlE~R{2+Rh^7%PA1H5X(p8%0TpJ=cqa$65XL)$#ign-y!qij3;2>j}I; ziO@O|aYfn&up5F`YtjGw68rD3{OSGNYmBnl?zdwY$=RFsegTZ=kkzRQ`r7ZjQP!H( zp4>)&zf<*N!tI00xzm-ME_a{_I!TbDCr;8E;kCH4LlL-tqLxDuBn-+xgPk37S&S2^ z2QZumkIimwz!c@!r0)j3*(jPIs*V!iLTRl0Cpt_UVNUgGZzdvs0(-yUghJfKr7;=h zD~y?OJ-bWJg;VdZ^r@vlDoeGV&8^--!t1AsIMZ5S440HCVr%uk- z2wV>!W1WCvFB~p$P$$_}|H5>uBeAe>`N1FI8AxM|pq%oNs;ED8x+tb44E) zTj{^fbh@eLi%5AqT?;d>Es5D*Fi{Bpk)q$^iF!!U`r2hHAO_?#!aYmf>G+jHsES4W zgpTKY59d?hsb~F0WE&dUp6lPt;Pm zcbTUqRryw^%{ViNW%Z(o8}dd00H(H-MmQmOiTq{}_rnwOr*Ybo7*}3W-qBT!#s0Ie z-s<1rvvJx_W;ViUD`04%1pra*Yw0BcGe)fDKUK8aF#BwBwMPU;9`!6E(~!043?SZx z13K%z@$$#2%2ovVlgFIPp7Q6(vO)ud)=*%ZSucL2Dh~K4B|%q4KnSpj#n@(0B})!9 z8p*hY@5)NDn^&Pmo;|!>erSYg`LkO?0FB@PLqRvc>4IsUM5O&>rRv|IBRxi(RX(gJ ztQ2;??L~&Mv;aVr5Q@(?y^DGo%pO^~zijld41aA0KKsy_6FeHIn?fNHP-z>$OoWer zjZ5hFQTy*-f7KENRiCE$ZOp4|+Wah|2=n@|W=o}bFM}Y@0e62+_|#fND5cwa3;P{^pEzlJbF1Yq^}>=wy8^^^$I2M_MH(4Dw{F6hm+vrWV5!q;oX z;tTNhz5`-V={ew|bD$?qcF^WPR{L(E%~XG8eJx(DoGzt2G{l8r!QPJ>kpHeOvCv#w zr=SSwMDaUX^*~v%6K%O~i)<^6`{go>a3IdfZ8hFmz&;Y@P%ZygShQZ2DSHd`m5AR= zx$wWU06;GYwXOf(%MFyj{8rPFXD};JCe85Bdp4$YJ2$TzZ7Gr#+SwCvBI1o$QP0(c zy`P51FEBV2HTisM3bHqpmECT@H!Y2-bv2*SoSPoO?wLe{M#zDTy@ujAZ!Izzky~3k zRA1RQIIoC*Mej1PH!sUgtkR0VCNMX(_!b65mo66iM*KQ7xT8t2eev$v#&YdUXKwGm z7okYAqYF&bveHeu6M5p9xheRCTiU8PFeb1_Rht0VVSbm%|1cOVobc8mvqcw!RjrMRM#~=7xibH&Fa5Imc|lZ{eC|R__)OrFg4@X_ ze+kk*_sDNG5^ELmHnZ7Ue?)#6!O)#Nv*Dl2mr#2)w{#i-;}0*_h4A%HidnmclH#;Q zmQbq+P4DS%3}PpPm7K_K3d2s#k~x+PlTul7+kIKol0@`YN1NG=+&PYTS->AdzPv!> zQvzT=)9se*Jr1Yq+C{wbK82gAX`NkbXFZ)4==j4t51{|-v!!$H8@WKA={d>CWRW+g z*`L>9rRucS`vbXu0rzA1#AQ(W?6)}1+oJSF=80Kf_2r~Qm-EJ6bbB3k`80rCv(0d` zvCf3;L2ovYG_TES%6vSuoKfIHC6w;V31!oqHM8-I8AFzcd^+_86!EcCOX|Ta9k1!s z_Vh(EGIIsI3fb&dF$9V8v(sTBC%!#<&KIGF;R+;MyC0~}$gC}}= zR`DbUVc&Bx`lYykFZ4{R{xRaUQkWCGCQlEc;!mf=+nOk$RUg*7 z;kP7CVLEc$CA7@6VFpsp3_t~m)W0aPxjsA3e5U%SfY{tp5BV5jH-5n?YX7*+U+Zs%LGR>U- z!x4Y_|4{gx?ZPJobISy991O znrmrC3otC;#4^&Rg_iK}XH(XX+eUHN0@Oe06hJk}F?`$)KmH^eWz@@N%wEc)%>?Ft z#9QAroDeyfztQ5Qe{m*#R#T%-h*&XvSEn@N$hYRTCMXS|EPwzF3IIysD2waj`vQD{ zv_#^Pgr?s~I*NE=acf@dWVRNWTr(GN0wrL)Z2=`Dr>}&ZDNX|+^Anl{Di%v1Id$_p zK5_H5`RDjJx`BW7hc85|> zHMMsWJ4KTMRHGu+vy*kBEMjz*^K8VtU=bXJYdhdZ-?jTXa$&n)C?QQIZ7ln$qbGlr zS*TYE+ppOrI@AoPP=VI-OXm}FzgXRL)OPvR$a_=SsC<3Jb+>5makX|U!}3lx4tX&L z^C<{9TggZNoeX!P1jX_K5HkEVnQ#s2&c#umzV6s2U-Q;({l+j^?hi7JnQ7&&*oOy9 z(|0asVTWUCiCnjcOnB2pN0DpuTglKq;&SFOQ3pUdye*eT<2()7WKbXp1qq9=bhMWlF-7BHT|i3TEIT77AcjD(v=I207wi-=vyiw5mxgPdTVUC z&h^FEUrXwWs9en2C{ywZp;nvS(Mb$8sBEh-*_d-OEm%~p1b2EpcwUdf<~zmJmaSTO zSX&&GGCEz-M^)G$fBvLC2q@wM$;n4jp+mt0MJFLuJ%c`tSp8$xuP|G81GEd2ci$|M z4XmH{5$j?rqDWoL4vs!}W&!?!rtj=6WKJcE>)?NVske(p;|#>vL|M_$as=mi-n-()a*OU3Okmk0wC<9y7t^D(er-&jEEak2!NnDiOQ99Wx8{S8}=Ng!e0tzj*#T)+%7;aM$ z&H}|o|J1p{IK0Q7JggAwipvHvko6>Epmh4RFRUr}$*2K4dz85o7|3#Bec9SQ4Y*;> zXWjT~f+d)dp_J`sV*!w>B%)#GI_;USp7?0810&3S=WntGZ)+tzhZ+!|=XlQ&@G@~3 z-dw@I1>9n1{+!x^Hz|xC+P#Ab`E@=vY?3%Bc!Po~e&&&)Qp85!I|U<-fCXy*wMa&t zgDk!l;gk;$taOCV$&60z+}_$ykz=Ea*)wJQ3-M|p*EK(cvtIre0Pta~(95J7zoxBN zS(yE^3?>88AL0Wfuou$BM{lR1hkrRibz=+I9ccwd`ZC*{NNqL)3pCcw^ygMmrG^Yp zn5f}Xf>%gncC=Yq96;rnfp4FQL#{!Y*->e82rHgY4Zwy{`JH}b9*qr^VA{%~Z}jtp z_t$PlS6}5{NtTqXHN?uI8ut8rOaD#F1C^ls73S=b_yI#iZDOGz3#^L@YheGd>L;<( z)U=iYj;`{>VDNzIxcjbTk-X3keXR8Xbc`A$o5# zKGSk-7YcoBYuAFFSCjGi;7b<;n-*`USs)IX z=0q6WZ=L!)PkYtZE-6)azhXV|+?IVGTOmMCHjhkBjfy@k1>?yFO3u!)@cl{fFAXnRYsWk)kpT?X{_$J=|?g@Q}+kFw|%n!;Zo}|HE@j=SFMvT8v`6Y zNO;tXN^036nOB2%=KzxB?n~NQ1K8IO*UE{;Xy;N^ZNI#P+hRZOaHATz9(=)w=QwV# z`z3+P>9b?l-@$@P3<;w@O1BdKh+H;jo#_%rr!ute{|YX4g5}n?O7Mq^01S5;+lABE+7`&_?mR_z7k|Ja#8h{!~j)| zbBX;*fsbUak_!kXU%HfJ2J+G7;inu#uRjMb|8a){=^))y236LDZ$$q3LRlat1D)%7K0!q5hT5V1j3qHc7MG9 z_)Q=yQ>rs>3%l=vu$#VVd$&IgO}Za#?aN!xY>-<3PhzS&q!N<=1Q7VJBfHjug^4|) z*fW^;%3}P7X#W3d;tUs3;`O&>;NKZBMR8au6>7?QriJ@gBaorz-+`pUWOP73DJL=M z(33uT6Gz@Sv40F6bN|H=lpcO z^AJl}&=TIjdevuDQ!w0K*6oZ2JBOhb31q!XDArFyKpz!I$p4|;c}@^bX{>AXdt7Bm zaLTk?c%h@%xq02reu~;t@$bv`b3i(P=g}~ywgSFpM;}b$zAD+=I!7`V~}ARB(Wx0C(EAq@?GuxOL9X+ffbkn3+Op0*80TqmpAq~EXmv%cq36celXmRz z%0(!oMp&2?`W)ALA&#|fu)MFp{V~~zIIixOxY^YtO5^FSox8v$#d0*{qk0Z)pNTt0QVZ^$`4vImEB>;Lo2!7K05TpY-sl#sWBz_W-aDIV`Ksabi zvpa#93Svo!70W*Ydh)Qzm{0?CU`y;T^ITg-J9nfWeZ-sbw)G@W?$Eomf%Bg2frfh5 zRm1{|E0+(4zXy){$}uC3%Y-mSA2-^I>Tw|gQx|7TDli_hB>``)Q^aZ`LJC2V3U$SABP}T)%}9g2pF9dT}aC~!rFFgkl1J$ z`^z{Arn3On-m%}r}TGF8KQe*OjSJ=T|caa_E;v89A{t@$yT^(G9=N9F?^kT*#s3qhJq!IH5|AhnqFd z0B&^gm3w;YbMNUKU>naBAO@fbz zqw=n!@--}o5;k6DvTW9pw)IJVz;X}ncbPVrmH>4x);8cx;q3UyiML1PWp%bxSiS|^ zC5!kc4qw%NSOGQ*Kcd#&$30=lDvs#*4W4q0u8E02U)7d=!W7+NouEyuF1dyH$D@G& zaFaxo9Ex|ZXA5y{eZT*i*dP~INSMAi@mvEX@q5i<&o&#sM}Df?Og8n8Ku4vOux=T% zeuw~z1hR}ZNwTn8KsQHKLwe2>p^K`YWUJEdVEl|mO21Bov!D0D$qPoOv=vJJ`)|%_ z>l%`eexY7t{BlVKP!`a^U@nM?#9OC*t76My_E_<16vCz1x_#82qj2PkWiMWgF8bM9 z(1t4VdHcJ;B~;Q%x01k_gQ0>u2*OjuEWNOGX#4}+N?Gb5;+NQMqp}Puqw2HnkYuKA zzKFWGHc&K>gwVgI1Sc9OT1s6fq=>$gZU!!xsilA$fF`kLdGoX*^t}ao@+^WBpk>`8 z4v_~gK|c2rCq#DZ+H)$3v~Hoi=)=1D==e3P zpKrRQ+>O^cyTuWJ%2}__0Z9SM_z9rptd*;-9uC1tDw4+A!=+K%8~M&+Zk#13hY$Y$ zo-8$*8dD5@}XDi19RjK6T^J~DIXbF5w&l?JLHMrf0 zLv0{7*G!==o|B%$V!a=EtVHdMwXLtmO~vl}P6;S(R2Q>*kTJK~!}gloxj)m|_LYK{ zl(f1cB=EON&wVFwK?MGn^nWuh@f95SHatPs(jcwSY#Dnl1@_gkOJ5=f`%s$ZHljRH0 z+c%lrb=Gi&N&1>^L_}#m>=U=(oT^vTA&3!xXNyqi$pdW1BDJ#^{h|2tZc{t^vag3& zAD7*8C`chNF|27itjBUo^CCDyEpJLX3&u+(L;YeeMwnXEoyN(ytoEabcl$lSgx~Ltatn}b$@j_yyMrBb03)shJE*$;Mw=;mZd&8e>IzE+4WIoH zCSZE7WthNUL$|Y#m!Hn?x7V1CK}V`KwW2D$-7&ODy5Cj;!_tTOOo1Mm%(RUt)#$@3 zhurA)t<7qik%%1Et+N1?R#hdBB#LdQ7{%-C zn$(`5e0eFh(#c*hvF>WT*07fk$N_631?W>kfjySN8^XC9diiOd#s?4tybICF;wBjp zIPzilX3{j%4u7blhq)tnaOBZ_`h_JqHXuI7SuIlNTgBk9{HIS&3|SEPfrvcE<@}E` zKk$y*nzsqZ{J{uWW9;#n=de&&h>m#A#q)#zRonr(?mDOYU&h&aQWD;?Z(22wY?t$U3qo`?{+amA$^TkxL+Ex2dh`q7iR&TPd0Ymwzo#b? zP$#t=elB5?k$#uE$K>C$YZbYUX_JgnXA`oF_Ifz4H7LEOW~{Gww&3s=wH4+j8*TU| zSX%LtJWqhr-xGNSe{;(16kxnak6RnZ{0qZ^kJI5X*It_YuynSpi(^-}Lolr{)#z_~ zw!(J-8%7Ybo^c3(mED`Xz8xecP35a6M8HarxRn%+NJBE;dw>>Y2T&;jzRd4FSDO3T zt*y+zXCtZQ0bP0yf6HRpD|WmzP;DR^-g^}{z~0x~z4j8m zucTe%k&S9Nt-?Jb^gYW1w6!Y3AUZ0Jcq;pJ)Exz%7k+mUOm6%ApjjSmflfKwBo6`B zhNb@$NHTJ>guaj9S{@DX)!6)b-Shav=DNKWy(V00k(D!v?PAR0f0vDNq*#mYmUp6> z76KxbFDw5U{{qx{BRj(>?|C`82ICKbfLxoldov-M?4Xl+3;I4GzLHyPOzYw7{WQST zPNYcx5onA%MAO9??41Po*1zW(Y%Zzn06-lUp{s<3!_9vv9HBjT02On0Hf$}NP;wF) zP<`2p3}A^~1YbvOh{ePMx$!JGUPX-tbBzp3mDZMY;}h;sQ->!p97GA)9a|tF(Gh{1$xk7 zUw?ELkT({Xw!KIr);kTRb1b|UL`r2_`a+&UFVCdJ)1T#fdh;71EQl9790Br0m_`$x z9|ZANuchFci8GNZ{XbP=+uXSJRe(;V5laQz$u18#?X*9}x7cIEbnr%<=1cX3EIu7$ zhHW6pe5M(&qEtsqRa>?)*{O;OJT+YUhG5{km|YI7I@JL_3Hwao9aXneiSA~a* z|Lp@c-oMNyeAEuUz{F?kuou3x#C*gU?lon!RC1s37gW^0Frc`lqQWH&(J4NoZg3m8 z;Lin#8Q+cFPD7MCzj}#|ws7b@?D9Q4dVjS4dpco=4yX5SSH=A@U@yqPdp@?g?qeia zH=Tt_9)G=6C2QIPsi-QipnK(mc0xXIN;j$WLf@n8eYvMk;*H-Q4tK%(3$CN}NGgO8n}fD~+>?<3UzvsrMf*J~%i;VKQHbF%TPalFi=#sgj)(P#SM^0Q=Tr>4kJVw8X3iWsP|e8tj}NjlMdWp z@2+M4HQu~3!=bZpjh;;DIDk&X}=c8~kn)FWWH z2KL1w^rA5&1@@^X%MjZ7;u(kH=YhH2pJPFQe=hn>tZd5RC5cfGYis8s9PKaxi*}-s6*W zRA^PwR=y^5Z){!(4D9-KC;0~;b*ploznFOaU`bJ_7U?qAi#mTo!&rIECRL$_y@yI27x2?W+zqDBD5~KCVYKFZLK+>ABC(Kj zeAll)KMgIlAG`r^rS{loBrGLtzhHY8$)<_S<(Dpkr(Ym@@vnQ&rS@FC*>2@XCH}M+an74WcRDcoQ+a3@A z9tYhl5$z7bMdTvD2r&jztBuo37?*k~wcU9GK2-)MTFS-lux-mIRYUuGUCI~V$?s#< z?1qAWb(?ZLm(N>%S%y10COdaq_Tm5c^%ooIxpR=`3e4C|@O5wY+eLik&XVi5oT7oe zmxH)Jd*5eo@!7t`x8!K=-+zJ-Sz)B_V$)s1pW~CDU$=q^&ABvf6S|?TOMB-RIm@CoFg>mjIQE)?+A1_3s6zmFU_oW&BqyMz1mY*IcP_2knjq5 zqw~JK(cVsmzc7*EvTT2rvpeqhg)W=%TOZ^>f`rD4|7Z5fq*2D^lpCttIg#ictgqZ$P@ru6P#f$x#KfnfTZj~LG6U_d-kE~`;kU_X)`H5so@?C zWmb!7x|xk@0L~0JFall*@ltyiL^)@3m4MqC7(7H0sH!WidId1#f#6R{Q&A!XzO1IAcIx;$k66dumt6lpUw@nL2MvqJ5^kbOVZ<^2jt5-njy|2@`07}0w z;M%I1$FCoLy`8xp8Tk)bFr;7aJeQ9KK6p=O$U0-&JYYy8woV*>b+FB?xLX`=pirYM z5K$BA(u)+jR{?O2r$c_Qvl?M{=Ar{yQ!UVsVn4k@0!b?_lA;dVz9uaQUgBH8Oz(Sb zrEs;&Ey>_ex8&!N{PmQjp+-Hlh|OA&wvDai#GpU=^-B70V0*LF=^bi+Nhe_o|azZ%~ZZ1$}LTmWt4aoB1 zPgccm$EwYU+jrdBaQFxQfn5gd(gM`Y*Ro1n&Zi?j=(>T3kmf94vdhf?AuS8>$Va#P zGL5F+VHpxdsCUa}+RqavXCobI-@B;WJbMphpK2%6t=XvKWWE|ruvREgM+|V=i6;;O zx$g=7^`$XWn0fu!gF=Xe9cMB8Z_SelD>&o&{1XFS`|nInK3BXlaeD*rc;R-#osyIS zWv&>~^TLIyBB6oDX+#>3<_0+2C4u2zK^wmHXXDD9_)kmLYJ!0SzM|%G9{pi)`X$uf zW}|%%#LgyK7m(4{V&?x_0KEDq56tk|0YNY~B(Sr|>WVz-pO3A##}$JCT}5P7DY+@W z#gJv>pA5>$|E3WO2tV7G^SuymB?tY`ooKcN3!vaQMnBNk-WATF{-$#}FyzgtJ8M^; zUK6KWSG)}6**+rZ&?o@PK3??uN{Q)#+bDP9i1W&j)oaU5d0bIWJ_9T5ac!qc?x66Q z$KUSZ`nYY94qfN_dpTFr8OW~A?}LD;Yty-BA)-be5Z3S#t2Io%q+cAbnGj1t$|qFR z9o?8B7OA^KjCYL=-!p}w(dkC^G6Nd%_I=1))PC0w5}ZZGJxfK)jP4Fwa@b-SYBw?% zdz9B-<`*B2dOn(N;mcTm%Do)rIvfXRNFX&1h`?>Rzuj~Wx)$p13nrDlS8-jwq@e@n zNIj_|8or==8~1h*Ih?w*8K7rYkGlwlTWAwLKc5}~dfz3y`kM&^Q|@C%1VAp_$wnw6zG~W4O+^ z>i?NY?oXf^Puc~+fDM$VgRNBpOZj{2cMP~gCqWAX4 z7>%$ux8@a&_B(pt``KSt;r+sR-$N;jdpY>|pyvPiN)9ohd*>mVST3wMo)){`B(&eX z1?zZJ-4u9NZ|~j1rdZYq4R$?swf}<6(#ex%7r{kh%U@kT)&kWuAszS%oJts=*OcL9 zaZwK<5DZw%1IFHXgFplP6JiL^dk8+SgM$D?8X+gE4172hXh!WeqIO>}$I9?Nry$*S zQ#f)RuH{P7RwA3v9f<-w>{PSzom;>(i&^l{E0(&Xp4A-*q-@{W1oE3K;1zb{&n28dSC2$N+6auXe0}e4b z)KLJ?5c*>@9K#I^)W;uU_Z`enquTUxr>mNq z1{0_puF-M7j${rs!dxxo3EelGodF1TvjV;Zpo;s{5f1pyCuRp=HDZ?s#IA4f?h|-p zGd|Mq^4hDa@Bh!c4ZE?O&x&XZ_ptZGYK4$9F4~{%R!}G1leCBx`dtNUS|K zL-7J5s4W@%mhXg1!}a4PD%!t&Qn%f_oquRajn3@C*)`o&K9o7V6DwzVMEhjVdDJ1fjhr#@=lp#@4EBqi=CCQ>73>R(>QKPNM&_Jpe5G`n4wegeC`FYEPJ{|vwS>$-`fuRSp3927qOv|NC3T3G-0 zA{K`|+tQy1yqE$ShWt8ny&5~)%ITb@^+x$w0)f&om;P8B)@}=Wzy59BwUfZ1vqw87 za2lB8J(&*l#(V}Id8SyQ0C(2amzkz3EqG&Ed0Jq1)$|&>4_|NIe=5|n=3?siFV0fI z{As5DLW^gs|B-b4C;Hd(SM-S~GQhzb>HgF2|2Usww0nL^;x@1eaB)=+Clj+$fF@H( z-fqP??~QMT$KI-#m;QC*&6vkp&8699G3)Bq0*kFZXINw=b9OVaed(3(3kS|IZ)CM? zJdnW&%t8MveBuK21uiYj)_a{Fnw0OErMzMN?d$QoPwkhOwcP&p+t>P)4tHlYw-pPN z^oJ=uc$Sl>pv@fZH~ZqxSvdhF@F1s=oZawpr^-#l{IIOGG=T%QXjtwPhIg-F@k@uIlr?J->Ia zpEUQ*=4g|XYn4Gez&aHr*;t$u3oODPmc2Ku)2Og|xjc%w;q!Zz+zY)*3{7V8bK4;& zYV82FZ+8?v)`J|G1w4I0fWdKg|2b#iaazCv;|?(W-q}$o&Y}Q5d@BRk^jL7#{kbCK zSgkyu;=DV+or2)AxCBgq-nj5=@n^`%T#V+xBGEkW4lCqrE)LMv#f;AvD__cQ@Eg3`~x| zW+h9mofSXCq5|M)9|ez(#X?-sxB%Go8};sJ?2abp(Y!lyi>k)|{M*Z$c{e1-K4ky` MPgg&ebxsLQ025IeI{*Lx diff --git a/quill_native_bridge/quill_native_bridge/example/web/index.html b/quill_native_bridge/quill_native_bridge/example/web/index.html deleted file mode 100644 index ec2fa4c1b..000000000 --- a/quill_native_bridge/quill_native_bridge/example/web/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - quill_native_bridge_example - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/web/manifest.json b/quill_native_bridge/quill_native_bridge/example/web/manifest.json deleted file mode 100644 index 7cc66ea86..000000000 --- a/quill_native_bridge/quill_native_bridge/example/web/manifest.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "quill_native_bridge_example", - "short_name": "quill_native_bridge_example", - "start_url": ".", - "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", - "description": "Demonstrates how to use the quill_native_bridge plugin.", - "orientation": "portrait-primary", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icons/Icon-512.png", - "sizes": "512x512", - "type": "image/png" - }, - { - "src": "icons/Icon-maskable-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "icons/Icon-maskable-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - } - ] -} diff --git a/quill_native_bridge/quill_native_bridge/example/windows/.gitignore b/quill_native_bridge/quill_native_bridge/example/windows/.gitignore deleted file mode 100644 index d492d0d98..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -flutter/ephemeral/ - -# Visual Studio user-specific files. -*.suo -*.user -*.userosscache -*.sln.docstates - -# Visual Studio build-related files. -x64/ -x86/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ diff --git a/quill_native_bridge/quill_native_bridge/example/windows/CMakeLists.txt b/quill_native_bridge/quill_native_bridge/example/windows/CMakeLists.txt deleted file mode 100644 index d960948af..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/CMakeLists.txt +++ /dev/null @@ -1,108 +0,0 @@ -# Project-level configuration. -cmake_minimum_required(VERSION 3.14) -project(example LANGUAGES CXX) - -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "example") - -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. -cmake_policy(VERSION 3.14...3.25) - -# Define build configuration option. -get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(IS_MULTICONFIG) - set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" - CACHE STRING "" FORCE) -else() - if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") - endif() -endif() -# Define settings for the Profile build mode. -set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") -set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") -set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") -set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") - -# Use Unicode for all projects. -add_definitions(-DUNICODE -D_UNICODE) - -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_17) - target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") - target_compile_options(${TARGET} PRIVATE /EHsc) - target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") - target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") -endfunction() - -# Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# Application build; see runner/CMakeLists.txt. -add_subdirectory("runner") - - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# Support files are copied into place next to the executable, so that it can -# run in place. This is done instead of making a separate bundle (as on Linux) -# so that building and running from within Visual Studio will work. -set(BUILD_BUNDLE_DIR "$") -# Make the "install" step default, as it's required to run. -set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -if(PLUGIN_BUNDLED_LIBRARIES) - install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() - -# Copy the native assets provided by the build.dart from all packages. -set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") -install(DIRECTORY "${NATIVE_ASSETS_DIR}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - CONFIGURATIONS Profile;Release - COMPONENT Runtime) diff --git a/quill_native_bridge/quill_native_bridge/example/windows/flutter/CMakeLists.txt b/quill_native_bridge/quill_native_bridge/example/windows/flutter/CMakeLists.txt deleted file mode 100644 index 903f4899d..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/flutter/CMakeLists.txt +++ /dev/null @@ -1,109 +0,0 @@ -# This file controls Flutter-level build steps. It should not be edited. -cmake_minimum_required(VERSION 3.14) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. -set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") - -# Set fallback configurations for older versions of the flutter tool. -if (NOT DEFINED FLUTTER_TARGET_PLATFORM) - set(FLUTTER_TARGET_PLATFORM "windows-x64") -endif() - -# === Flutter Library === -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "flutter_export.h" - "flutter_windows.h" - "flutter_messenger.h" - "flutter_plugin_registrar.h" - "flutter_texture_registrar.h" -) -list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") -add_dependencies(flutter flutter_assemble) - -# === Wrapper === -list(APPEND CPP_WRAPPER_SOURCES_CORE - "core_implementations.cc" - "standard_codec.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_PLUGIN - "plugin_registrar.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_APP - "flutter_engine.cc" - "flutter_view_controller.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") - -# Wrapper sources needed for a plugin. -add_library(flutter_wrapper_plugin STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} -) -apply_standard_settings(flutter_wrapper_plugin) -set_target_properties(flutter_wrapper_plugin PROPERTIES - POSITION_INDEPENDENT_CODE ON) -set_target_properties(flutter_wrapper_plugin PROPERTIES - CXX_VISIBILITY_PRESET hidden) -target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) -target_include_directories(flutter_wrapper_plugin PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_plugin flutter_assemble) - -# Wrapper sources needed for the runner. -add_library(flutter_wrapper_app STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_APP} -) -apply_standard_settings(flutter_wrapper_app) -target_link_libraries(flutter_wrapper_app PUBLIC flutter) -target_include_directories(flutter_wrapper_app PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_app flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") -set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} - ${PHONY_OUTPUT} - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - ${FLUTTER_TARGET_PLATFORM} $ - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} -) diff --git a/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.cc b/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.cc deleted file mode 100644 index 8b6d4680a..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void RegisterPlugins(flutter::PluginRegistry* registry) { -} diff --git a/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.h b/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.h deleted file mode 100644 index dc139d85a..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include - -// Registers Flutter plugins. -void RegisterPlugins(flutter::PluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugins.cmake b/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/CMakeLists.txt b/quill_native_bridge/quill_native_bridge/example/windows/runner/CMakeLists.txt deleted file mode 100644 index 394917c05..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/runner/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -project(runner LANGUAGES CXX) - -# Define the application target. To change its name, change BINARY_NAME in the -# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer -# work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} WIN32 - "flutter_window.cpp" - "main.cpp" - "utils.cpp" - "win32_window.cpp" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" - "Runner.rc" - "runner.exe.manifest" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add preprocessor definitions for the build version. -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") - -# Disable Windows macros that collide with C++ standard library functions. -target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") - -# Add dependency libraries and include directories. Add any application-specific -# dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) -target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") -target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") - -# Run the Flutter tool portions of the build. This must not be removed. -add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/Runner.rc b/quill_native_bridge/quill_native_bridge/example/windows/runner/Runner.rc deleted file mode 100644 index 54797c97d..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/runner/Runner.rc +++ /dev/null @@ -1,121 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// -#pragma code_page(65001) -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_APP_ICON ICON "resources\\app_icon.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) -#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD -#else -#define VERSION_AS_NUMBER 1,0,0,0 -#endif - -#if defined(FLUTTER_VERSION) -#define VERSION_AS_STRING FLUTTER_VERSION -#else -#define VERSION_AS_STRING "1.0.0" -#endif - -VS_VERSION_INFO VERSIONINFO - FILEVERSION VERSION_AS_NUMBER - PRODUCTVERSION VERSION_AS_NUMBER - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_APP - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904e4" - BEGIN - VALUE "CompanyName", "dev.flutterquill" "\0" - VALUE "FileDescription", "example" "\0" - VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "example" "\0" - VALUE "LegalCopyright", "Copyright (C) 2024 dev.flutterquill. All rights reserved." "\0" - VALUE "OriginalFilename", "example.exe" "\0" - VALUE "ProductName", "example" "\0" - VALUE "ProductVersion", VERSION_AS_STRING "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 - END -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.cpp b/quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.cpp deleted file mode 100644 index 955ee3038..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "flutter_window.h" - -#include - -#include "flutter/generated_plugin_registrant.h" - -FlutterWindow::FlutterWindow(const flutter::DartProject& project) - : project_(project) {} - -FlutterWindow::~FlutterWindow() {} - -bool FlutterWindow::OnCreate() { - if (!Win32Window::OnCreate()) { - return false; - } - - RECT frame = GetClientArea(); - - // The size here must match the window dimensions to avoid unnecessary surface - // creation / destruction in the startup path. - flutter_controller_ = std::make_unique( - frame.right - frame.left, frame.bottom - frame.top, project_); - // Ensure that basic setup of the controller was successful. - if (!flutter_controller_->engine() || !flutter_controller_->view()) { - return false; - } - RegisterPlugins(flutter_controller_->engine()); - SetChildContent(flutter_controller_->view()->GetNativeWindow()); - - flutter_controller_->engine()->SetNextFrameCallback([&]() { - this->Show(); - }); - - // Flutter can complete the first frame before the "show window" callback is - // registered. The following call ensures a frame is pending to ensure the - // window is shown. It is a no-op if the first frame hasn't completed yet. - flutter_controller_->ForceRedraw(); - - return true; -} - -void FlutterWindow::OnDestroy() { - if (flutter_controller_) { - flutter_controller_ = nullptr; - } - - Win32Window::OnDestroy(); -} - -LRESULT -FlutterWindow::MessageHandler(HWND hwnd, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - // Give Flutter, including plugins, an opportunity to handle window messages. - if (flutter_controller_) { - std::optional result = - flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, - lparam); - if (result) { - return *result; - } - } - - switch (message) { - case WM_FONTCHANGE: - flutter_controller_->engine()->ReloadSystemFonts(); - break; - } - - return Win32Window::MessageHandler(hwnd, message, wparam, lparam); -} diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.h b/quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.h deleted file mode 100644 index 6da0652f0..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/runner/flutter_window.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef RUNNER_FLUTTER_WINDOW_H_ -#define RUNNER_FLUTTER_WINDOW_H_ - -#include -#include - -#include - -#include "win32_window.h" - -// A window that does nothing but host a Flutter view. -class FlutterWindow : public Win32Window { - public: - // Creates a new FlutterWindow hosting a Flutter view running |project|. - explicit FlutterWindow(const flutter::DartProject& project); - virtual ~FlutterWindow(); - - protected: - // Win32Window: - bool OnCreate() override; - void OnDestroy() override; - LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept override; - - private: - // The project to run. - flutter::DartProject project_; - - // The Flutter instance hosted by this window. - std::unique_ptr flutter_controller_; -}; - -#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/main.cpp b/quill_native_bridge/quill_native_bridge/example/windows/runner/main.cpp deleted file mode 100644 index a61bf80d3..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/runner/main.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#include - -#include "flutter_window.h" -#include "utils.h" - -int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, - _In_ wchar_t *command_line, _In_ int show_command) { - // Attach to console when present (e.g., 'flutter run') or create a - // new console when running with a debugger. - if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { - CreateAndAttachConsole(); - } - - // Initialize COM, so that it is available for use in the library and/or - // plugins. - ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - - flutter::DartProject project(L"data"); - - std::vector command_line_arguments = - GetCommandLineArguments(); - - project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); - - FlutterWindow window(project); - Win32Window::Point origin(10, 10); - Win32Window::Size size(1280, 720); - if (!window.Create(L"example", origin, size)) { - return EXIT_FAILURE; - } - window.SetQuitOnClose(true); - - ::MSG msg; - while (::GetMessage(&msg, nullptr, 0, 0)) { - ::TranslateMessage(&msg); - ::DispatchMessage(&msg); - } - - ::CoUninitialize(); - return EXIT_SUCCESS; -} diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/resource.h b/quill_native_bridge/quill_native_bridge/example/windows/runner/resource.h deleted file mode 100644 index 66a65d1e4..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/runner/resource.h +++ /dev/null @@ -1,16 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by Runner.rc -// -#define IDI_APP_ICON 101 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/resources/app_icon.ico b/quill_native_bridge/quill_native_bridge/example/windows/runner/resources/app_icon.ico deleted file mode 100644 index c04e20caf6370ebb9253ad831cc31de4a9c965f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33772 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 diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/runner.exe.manifest b/quill_native_bridge/quill_native_bridge/example/windows/runner/runner.exe.manifest deleted file mode 100644 index 153653e8d..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/runner/runner.exe.manifest +++ /dev/null @@ -1,14 +0,0 @@ - - - - - PerMonitorV2 - - - - - - - - - diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/utils.cpp b/quill_native_bridge/quill_native_bridge/example/windows/runner/utils.cpp deleted file mode 100644 index 3a0b46511..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/runner/utils.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "utils.h" - -#include -#include -#include -#include - -#include - -void CreateAndAttachConsole() { - if (::AllocConsole()) { - FILE *unused; - if (freopen_s(&unused, "CONOUT$", "w", stdout)) { - _dup2(_fileno(stdout), 1); - } - if (freopen_s(&unused, "CONOUT$", "w", stderr)) { - _dup2(_fileno(stdout), 2); - } - std::ios::sync_with_stdio(); - FlutterDesktopResyncOutputStreams(); - } -} - -std::vector GetCommandLineArguments() { - // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. - int argc; - wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - if (argv == nullptr) { - return std::vector(); - } - - std::vector command_line_arguments; - - // Skip the first argument as it's the binary name. - for (int i = 1; i < argc; i++) { - command_line_arguments.push_back(Utf8FromUtf16(argv[i])); - } - - ::LocalFree(argv); - - return command_line_arguments; -} - -std::string Utf8FromUtf16(const wchar_t* utf16_string) { - if (utf16_string == nullptr) { - return std::string(); - } - unsigned int target_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, nullptr, 0, nullptr, nullptr) - -1; // remove the trailing null character - int input_length = (int)wcslen(utf16_string); - std::string utf8_string; - if (target_length == 0 || target_length > utf8_string.max_size()) { - return utf8_string; - } - utf8_string.resize(target_length); - int converted_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - input_length, utf8_string.data(), target_length, nullptr, nullptr); - if (converted_length == 0) { - return std::string(); - } - return utf8_string; -} diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/utils.h b/quill_native_bridge/quill_native_bridge/example/windows/runner/utils.h deleted file mode 100644 index 3879d5475..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/runner/utils.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef RUNNER_UTILS_H_ -#define RUNNER_UTILS_H_ - -#include -#include - -// Creates a console for the process, and redirects stdout and stderr to -// it for both the runner and the Flutter library. -void CreateAndAttachConsole(); - -// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string -// encoded in UTF-8. Returns an empty std::string on failure. -std::string Utf8FromUtf16(const wchar_t* utf16_string); - -// Gets the command line arguments passed in as a std::vector, -// encoded in UTF-8. Returns an empty std::vector on failure. -std::vector GetCommandLineArguments(); - -#endif // RUNNER_UTILS_H_ diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.cpp b/quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.cpp deleted file mode 100644 index 60608d0fe..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.cpp +++ /dev/null @@ -1,288 +0,0 @@ -#include "win32_window.h" - -#include -#include - -#include "resource.h" - -namespace { - -/// Window attribute that enables dark mode window decorations. -/// -/// Redefined in case the developer's machine has a Windows SDK older than -/// version 10.0.22000.0. -/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute -#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE -#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 -#endif - -constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; - -/// Registry key for app theme preference. -/// -/// A value of 0 indicates apps should use dark mode. A non-zero or missing -/// value indicates apps should use light mode. -constexpr const wchar_t kGetPreferredBrightnessRegKey[] = - L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; -constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; - -// The number of Win32Window objects that currently exist. -static int g_active_window_count = 0; - -using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); - -// Scale helper to convert logical scaler values to physical using passed in -// scale factor -int Scale(int source, double scale_factor) { - return static_cast(source * scale_factor); -} - -// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. -// This API is only needed for PerMonitor V1 awareness mode. -void EnableFullDpiSupportIfAvailable(HWND hwnd) { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) { - return; - } - auto enable_non_client_dpi_scaling = - reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) { - enable_non_client_dpi_scaling(hwnd); - } - FreeLibrary(user32_module); -} - -} // namespace - -// Manages the Win32Window's window class registration. -class WindowClassRegistrar { - public: - ~WindowClassRegistrar() = default; - - // Returns the singleton registrar instance. - static WindowClassRegistrar* GetInstance() { - if (!instance_) { - instance_ = new WindowClassRegistrar(); - } - return instance_; - } - - // Returns the name of the window class, registering the class if it hasn't - // previously been registered. - const wchar_t* GetWindowClass(); - - // Unregisters the window class. Should only be called if there are no - // instances of the window. - void UnregisterWindowClass(); - - private: - WindowClassRegistrar() = default; - - static WindowClassRegistrar* instance_; - - bool class_registered_ = false; -}; - -WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; - -const wchar_t* WindowClassRegistrar::GetWindowClass() { - if (!class_registered_) { - WNDCLASS window_class{}; - window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); - window_class.lpszClassName = kWindowClassName; - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = GetModuleHandle(nullptr); - window_class.hIcon = - LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); - window_class.hbrBackground = 0; - window_class.lpszMenuName = nullptr; - window_class.lpfnWndProc = Win32Window::WndProc; - RegisterClass(&window_class); - class_registered_ = true; - } - return kWindowClassName; -} - -void WindowClassRegistrar::UnregisterWindowClass() { - UnregisterClass(kWindowClassName, nullptr); - class_registered_ = false; -} - -Win32Window::Win32Window() { - ++g_active_window_count; -} - -Win32Window::~Win32Window() { - --g_active_window_count; - Destroy(); -} - -bool Win32Window::Create(const std::wstring& title, - const Point& origin, - const Size& size) { - Destroy(); - - const wchar_t* window_class = - WindowClassRegistrar::GetInstance()->GetWindowClass(); - - const POINT target_point = {static_cast(origin.x), - static_cast(origin.y)}; - HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); - UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); - double scale_factor = dpi / 96.0; - - HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW, - Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), - Scale(size.width, scale_factor), Scale(size.height, scale_factor), - nullptr, nullptr, GetModuleHandle(nullptr), this); - - if (!window) { - return false; - } - - UpdateTheme(window); - - return OnCreate(); -} - -bool Win32Window::Show() { - return ShowWindow(window_handle_, SW_SHOWNORMAL); -} - -// static -LRESULT CALLBACK Win32Window::WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - if (message == WM_NCCREATE) { - auto window_struct = reinterpret_cast(lparam); - SetWindowLongPtr(window, GWLP_USERDATA, - reinterpret_cast(window_struct->lpCreateParams)); - - auto that = static_cast(window_struct->lpCreateParams); - EnableFullDpiSupportIfAvailable(window); - that->window_handle_ = window; - } else if (Win32Window* that = GetThisFromHandle(window)) { - return that->MessageHandler(window, message, wparam, lparam); - } - - return DefWindowProc(window, message, wparam, lparam); -} - -LRESULT -Win32Window::MessageHandler(HWND hwnd, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - switch (message) { - case WM_DESTROY: - window_handle_ = nullptr; - Destroy(); - if (quit_on_close_) { - PostQuitMessage(0); - } - return 0; - - case WM_DPICHANGED: { - auto newRectSize = reinterpret_cast(lparam); - LONG newWidth = newRectSize->right - newRectSize->left; - LONG newHeight = newRectSize->bottom - newRectSize->top; - - SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, - newHeight, SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; - } - case WM_SIZE: { - RECT rect = GetClientArea(); - if (child_content_ != nullptr) { - // Size and position the child window. - MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, TRUE); - } - return 0; - } - - case WM_ACTIVATE: - if (child_content_ != nullptr) { - SetFocus(child_content_); - } - return 0; - - case WM_DWMCOLORIZATIONCOLORCHANGED: - UpdateTheme(hwnd); - return 0; - } - - return DefWindowProc(window_handle_, message, wparam, lparam); -} - -void Win32Window::Destroy() { - OnDestroy(); - - if (window_handle_) { - DestroyWindow(window_handle_); - window_handle_ = nullptr; - } - if (g_active_window_count == 0) { - WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); - } -} - -Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { - return reinterpret_cast( - GetWindowLongPtr(window, GWLP_USERDATA)); -} - -void Win32Window::SetChildContent(HWND content) { - child_content_ = content; - SetParent(content, window_handle_); - RECT frame = GetClientArea(); - - MoveWindow(content, frame.left, frame.top, frame.right - frame.left, - frame.bottom - frame.top, true); - - SetFocus(child_content_); -} - -RECT Win32Window::GetClientArea() { - RECT frame; - GetClientRect(window_handle_, &frame); - return frame; -} - -HWND Win32Window::GetHandle() { - return window_handle_; -} - -void Win32Window::SetQuitOnClose(bool quit_on_close) { - quit_on_close_ = quit_on_close; -} - -bool Win32Window::OnCreate() { - // No-op; provided for subclasses. - return true; -} - -void Win32Window::OnDestroy() { - // No-op; provided for subclasses. -} - -void Win32Window::UpdateTheme(HWND const window) { - DWORD light_mode; - DWORD light_mode_size = sizeof(light_mode); - LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, - kGetPreferredBrightnessRegValue, - RRF_RT_REG_DWORD, nullptr, &light_mode, - &light_mode_size); - - if (result == ERROR_SUCCESS) { - BOOL enable_dark_mode = light_mode == 0; - DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, - &enable_dark_mode, sizeof(enable_dark_mode)); - } -} diff --git a/quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.h b/quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.h deleted file mode 100644 index e901dde68..000000000 --- a/quill_native_bridge/quill_native_bridge/example/windows/runner/win32_window.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef RUNNER_WIN32_WINDOW_H_ -#define RUNNER_WIN32_WINDOW_H_ - -#include - -#include -#include -#include - -// A class abstraction for a high DPI-aware Win32 Window. Intended to be -// inherited from by classes that wish to specialize with custom -// rendering and input handling -class Win32Window { - public: - struct Point { - unsigned int x; - unsigned int y; - Point(unsigned int x, unsigned int y) : x(x), y(y) {} - }; - - struct Size { - unsigned int width; - unsigned int height; - Size(unsigned int width, unsigned int height) - : width(width), height(height) {} - }; - - Win32Window(); - virtual ~Win32Window(); - - // Creates a win32 window with |title| that is positioned and sized using - // |origin| and |size|. New windows are created on the default monitor. Window - // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size this function will scale the inputted width and height as - // as appropriate for the default monitor. The window is invisible until - // |Show| is called. Returns true if the window was created successfully. - bool Create(const std::wstring& title, const Point& origin, const Size& size); - - // Show the current window. Returns true if the window was successfully shown. - bool Show(); - - // Release OS resources associated with window. - void Destroy(); - - // Inserts |content| into the window tree. - void SetChildContent(HWND content); - - // Returns the backing Window handle to enable clients to set icon and other - // window properties. Returns nullptr if the window has been destroyed. - HWND GetHandle(); - - // If true, closing this window will quit the application. - void SetQuitOnClose(bool quit_on_close); - - // Return a RECT representing the bounds of the current client area. - RECT GetClientArea(); - - protected: - // Processes and route salient window messages for mouse handling, - // size change and DPI. Delegates handling of these to member overloads that - // inheriting classes can handle. - virtual LRESULT MessageHandler(HWND window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Called when CreateAndShow is called, allowing subclass window-related - // setup. Subclasses should return false if setup fails. - virtual bool OnCreate(); - - // Called when Destroy is called. - virtual void OnDestroy(); - - private: - friend class WindowClassRegistrar; - - // OS callback called by message pump. Handles the WM_NCCREATE message which - // is passed when the non-client area is being created and enables automatic - // non-client DPI scaling so that the non-client area automatically - // responds to changes in DPI. All other messages are handled by - // MessageHandler. - static LRESULT CALLBACK WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Retrieves a class instance pointer for |window| - static Win32Window* GetThisFromHandle(HWND const window) noexcept; - - // Update the window frame's theme to match the system theme. - static void UpdateTheme(HWND const window); - - bool quit_on_close_ = false; - - // window handle for top level window. - HWND window_handle_ = nullptr; - - // window handle for hosted content. - HWND child_content_ = nullptr; -}; - -#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart deleted file mode 100644 index 850991f2e..000000000 --- a/quill_native_bridge/quill_native_bridge/lib/quill_native_bridge.dart +++ /dev/null @@ -1,105 +0,0 @@ -/// An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) -/// package to access platform-specific APIs. -library; - -import 'package:flutter/foundation.dart' - show TargetPlatform, Uint8List, defaultTargetPlatform, kIsWeb; - -import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; - -export 'package:quill_native_bridge_platform_interface/src/platform_feature.dart' - show QuillNativeBridgeFeature; - -/// An internal plugin for [`flutter_quill`](https://pub.dev/packages/flutter_quill) -/// package to access platform-specific APIs. -/// -/// See [QuillNativeBridgeFeature] to check whatever if a feature is supported. -class QuillNativeBridge { - QuillNativeBridge._(); - - static QuillNativeBridgePlatform get _platform => - QuillNativeBridgePlatform.instance; - - /// Check if the app is running on [iOS Simulator](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device). - /// - /// Should only be called when [defaultTargetPlatform] - /// is [TargetPlatform.iOS] and [kIsWeb] is `false`. - static Future isIOSSimulator() => _platform.isIOSSimulator(); - - /// Checks if the specified [feature] is supported in the current implementation. - /// - /// Will verify if this is supported in the platform itself: - /// - /// - If [feature] is supported on **Android API 21** (as an example) and the - /// current Android API is `19` then will return `false` - /// - If [feature] is supported on the web if Clipboard API (as another example) - /// available in the current browser, and the current browser doesn't support it, - /// will return `false` too. For this specific example, you will need - /// to fallback to **Clipboard events** on **Firefox** or browsers that doesn't - /// support **Clipboard API**. - /// - /// Always check the docs of the method you're calling to see if there - /// are special notes. - static Future isSupported(QuillNativeBridgeFeature feature) => - _platform.isSupported(feature); - - /// Return HTML from the Clipboard. - /// - /// **Important for web**: If [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) - /// is not supported on the web browser, should fallback to [Clipboard Events](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) - /// such as the [paste_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event). - /// - /// The HTML can be platform-dependent. - /// - /// Returns `null` if the HTML content is not available or if the user has not granted - /// permission for pasting (on some platforms such as iOS). - /// - /// Currently only supports **Android**, **iOS**, **macOS**, **Windows**, **Linux**, and the **Web**. - static Future getClipboardHtml() => _platform.getClipboardHtml(); - - /// Copy the [html] to the clipboard to be pasted on other apps. - /// - /// **Important for web**: If [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) - /// is not supported on the web browser, should fallback to [Clipboard Events](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) - /// such as the [copy_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/copy_event). - /// - /// Currently only supports **Android**, **iOS**, **macOS**, **Linux**, **Windows** and the **Web**. - static Future copyHtmlToClipboard(String html) => - _platform.copyHtmlToClipboard(html); - - /// Copy the [imageBytes] to Clipboard to be pasted on other apps. - /// - /// Require modifying `AndroidManifest.xml` to work on **Android**. - /// Otherwise, you will get a warning available only on debug-builds. - /// See: https://github.com/singerdmx/flutter-quill#-platform-specific-configurations - /// - /// **Important for web**: If [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) - /// is not supported on the web browser, should fallback to [Clipboard Events](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) - /// such as the [copy_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/copy_event). - /// - /// - /// Currently only supports **Android**, **iOS**, **macOS**, **Linux**, and the **Web**. - static Future copyImageToClipboard(Uint8List imageBytes) => - _platform.copyImageToClipboard(imageBytes); - - /// Return the copied image from the Clipboard. - /// - /// **Important for web**: If [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) - /// is not supported on the web browser, should fallback to [Clipboard Events](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) - /// such as the [paste_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event). - /// - /// Currently only supports **Android**, **iOS**, **macOS**, **Linux**, and the **Web**. - static Future getClipboardImage() => - _platform.getClipboardImage(); - - /// Return the copied gif from the Clipboard. - /// - /// Currently only supports **Android**, **iOS**. - static Future getClipboardGif() => _platform.getClipboardGif(); - - /// Return the file paths from the Clipboard. - /// - /// Currently only supports **macOS** and **Linux**. - static Future> getClipboardFiles() => - _platform.getClipboardFiles(); -} diff --git a/quill_native_bridge/quill_native_bridge/pubspec.yaml b/quill_native_bridge/quill_native_bridge/pubspec.yaml deleted file mode 100644 index d6c034697..000000000 --- a/quill_native_bridge/quill_native_bridge/pubspec.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: quill_native_bridge -description: "An internal plugin for flutter_quill package to access platform-specific APIs" -version: 10.7.7 -homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge -repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge -issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ -documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge - -platforms: - android: - ios: - macos: - web: - linux: - windows: - -environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' - -dependencies: - flutter: - sdk: flutter - quill_native_bridge_android: ^0.0.1-dev.1 - quill_native_bridge_platform_interface: ^0.0.1-dev.2 - quill_native_bridge_web: ^0.0.1-dev.2 - quill_native_bridge_windows: ^0.0.1-dev.1 - quill_native_bridge_linux: ^0.0.1-dev.1 - quill_native_bridge_ios: ^0.0.1-dev.1 - quill_native_bridge_macos: ^0.0.1-dev.1 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^5.0.0 - -flutter: - plugin: - platforms: - android: - default_package: quill_native_bridge_android - ios: - default_package: quill_native_bridge_ios - macos: - default_package: quill_native_bridge_macos - web: - default_package: quill_native_bridge_web - windows: - default_package: quill_native_bridge_windows - linux: - default_package: quill_native_bridge_linux \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml deleted file mode 100644 index 27bfdc2a6..000000000 --- a/quill_native_bridge/quill_native_bridge/pubspec_overrides.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing -dependency_overrides: - quill_native_bridge_web: - path: ../quill_native_bridge_web - quill_native_bridge_windows: - path: ../quill_native_bridge_windows - quill_native_bridge_linux: - path: ../quill_native_bridge_linux - quill_native_bridge_android: - path: ../quill_native_bridge_android - quill_native_bridge_ios: - path: ../quill_native_bridge_ios - quill_native_bridge_macos: - path: ../quill_native_bridge_macos - quill_native_bridge_platform_interface: - path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_android/CHANGELOG.md deleted file mode 100644 index 13b5ca517..000000000 --- a/quill_native_bridge/quill_native_bridge_android/CHANGELOG.md +++ /dev/null @@ -1,11 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -## 0.0.1-dev.1 - -- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. - -## 0.0.1-dev.0 - -- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/LICENSE b/quill_native_bridge/quill_native_bridge_android/LICENSE deleted file mode 100644 index e7ff73e1b..000000000 --- a/quill_native_bridge/quill_native_bridge_android/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_android/README.md b/quill_native_bridge/quill_native_bridge_android/README.md deleted file mode 100644 index 4c8cc6c8d..000000000 --- a/quill_native_bridge/quill_native_bridge_android/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# đŸĒļ Quill Native Bridge - -The Android implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). - -## ⚙ī¸ Usage - -This package is endorsed, which means you can simply use `quill_native_bridge` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. - -However, if you import this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. - -## 📉 Note on breaking changes - -The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/android/.gitignore b/quill_native_bridge/quill_native_bridge_android/android/.gitignore deleted file mode 100644 index 161bdcdaf..000000000 --- a/quill_native_bridge/quill_native_bridge_android/android/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea/workspace.xml -/.idea/libraries -.DS_Store -/build -/captures -.cxx diff --git a/quill_native_bridge/quill_native_bridge_android/android/build.gradle b/quill_native_bridge/quill_native_bridge_android/android/build.gradle deleted file mode 100644 index 6232173a3..000000000 --- a/quill_native_bridge/quill_native_bridge_android/android/build.gradle +++ /dev/null @@ -1,54 +0,0 @@ -group = "dev.flutterquill.quill_native_bridge" -version = "1.0-SNAPSHOT" - -buildscript { - ext.kotlin_version = "1.8.22" - repositories { - google() - mavenCentral() - } - - dependencies { - classpath("com.android.tools.build:gradle:8.1.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 = "dev.flutterquill.quill_native_bridge" - } - - // Using outdated compileSdk can prevent apps - // from build using newer API - // https://github.com/flutter/flutter/issues/63533 - 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 - } -} diff --git a/quill_native_bridge/quill_native_bridge_android/android/settings.gradle b/quill_native_bridge/quill_native_bridge_android/android/settings.gradle deleted file mode 100644 index f757e6f01..000000000 --- a/quill_native_bridge/quill_native_bridge_android/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'quill_native_bridge' diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/AndroidManifest.xml b/quill_native_bridge/quill_native_bridge_android/android/src/main/AndroidManifest.xml deleted file mode 100644 index eb0654732..000000000 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgeImpl.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgeImpl.kt deleted file mode 100644 index c8c6abf54..000000000 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgeImpl.kt +++ /dev/null @@ -1,28 +0,0 @@ -package dev.flutterquill.quill_native_bridge - -import android.content.Context -import dev.flutterquill.quill_native_bridge.clipboard.ClipboardReadImageHandler -import dev.flutterquill.quill_native_bridge.clipboard.ClipboardRichTextHandler -import dev.flutterquill.quill_native_bridge.clipboard.ClipboardWriteImageHandler -import dev.flutterquill.quill_native_bridge.generated.QuillNativeBridgeApi - -class QuillNativeBridgeImpl(private val context: Context) : QuillNativeBridgeApi { - override fun getClipboardHtml(): String? = ClipboardRichTextHandler.getClipboardHtml(context) - - override fun copyHtmlToClipboard(html: String) = - ClipboardRichTextHandler.copyHtmlToClipboard(context, html) - - override fun getClipboardImage(): ByteArray? = ClipboardReadImageHandler.getClipboardImage( - context, - // Will convert the image to PNG - imageType = ClipboardReadImageHandler.ImageType.AnyExceptGif, - ) - - override fun copyImageToClipboard(imageBytes: ByteArray) = - ClipboardWriteImageHandler.copyImageToClipboard(context, imageBytes) - - override fun getClipboardGif(): ByteArray? = ClipboardReadImageHandler.getClipboardImage( - context, - imageType = ClipboardReadImageHandler.ImageType.Gif, - ) -} \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt deleted file mode 100644 index 62249993e..000000000 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/QuillNativeBridgePlugin.kt +++ /dev/null @@ -1,19 +0,0 @@ -package dev.flutterquill.quill_native_bridge - -import dev.flutterquill.quill_native_bridge.generated.QuillNativeBridgeApi -import io.flutter.embedding.engine.plugins.FlutterPlugin - -class QuillNativeBridgePlugin : FlutterPlugin { - private var api: QuillNativeBridgeApi? = null - - override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { - api = QuillNativeBridgeImpl(binding.applicationContext) - requireNotNull(api) { "A new instance of $QuillNativeBridgeApi was created that appeared to be null" } - QuillNativeBridgeApi.setUp(binding.binaryMessenger, api) - } - - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - QuillNativeBridgeApi.setUp(binding.binaryMessenger, api) - api = null - } -} diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt deleted file mode 100644 index b2b805dc4..000000000 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardReadImageHandler.kt +++ /dev/null @@ -1,242 +0,0 @@ -package dev.flutterquill.quill_native_bridge.clipboard - -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.ImageDecoder -import android.net.Uri -import android.os.Build -import androidx.core.graphics.decodeBitmap -import dev.flutterquill.quill_native_bridge.generated.FlutterError -import java.io.ByteArrayOutputStream -import java.io.FileNotFoundException - -object ClipboardReadImageHandler { - private const val MIME_TYPE_IMAGE_ALL = "image/*" - private const val MIME_TYPE_IMAGE_PNG = "image/png" - private const val MIME_TYPE_IMAGE_JPEG = "image/jpeg" - private const val MIME_TYPE_IMAGE_GIF = "image/gif" - - /** - * The media/image type. - * - * @property Png [MIME_TYPE_IMAGE_PNG] - * @property AnyExceptGif All images that are [MIME_TYPE_IMAGE_ALL] but not [MIME_TYPE_IMAGE_GIF] - * @property Gif [MIME_TYPE_IMAGE_GIF] - * @property Jpeg [MIME_TYPE_IMAGE_JPEG] - * */ - enum class ImageType { Png, Jpeg, AnyExceptGif, Gif } - - /** - * Read the primary clip of the system clipboard using [ClipboardManager] - * */ - private fun getPrimaryClip(context: Context): ClipData? { - val clipboard = - context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - - if (!clipboard.hasPrimaryClip()) { - return null - } - - val clipData = clipboard.primaryClip - - if (clipData == null || clipData.itemCount <= 0) { - return null - } - - return clipData - } - - /** - * Return Image URI from [clipData]. - * - * If the [ClipData.Item.getUri] is `null` then will check if the [clipData] - * is a text containing the file path, parse it and return the [Uri]. - * - * Should check if can read the [Uri] even if it's non null using [readOrThrow] otherwise a exception - * can arise when the app no longer have access to the [Uri]. - * - * @param clipData The clip data to extract the [Uri] from. - * @param imageType The type of the image whatever if it's png, gif or any. - * */ - private fun getImageUri( - clipData: ClipData, - imageType: ImageType, - ): Uri? { - val clipboardItem = clipData.getItemAt(0) - - val imageUri = clipboardItem.uri - val matchMimeType: Boolean = when (imageType) { - ImageType.Png -> clipData.description.hasMimeType(MIME_TYPE_IMAGE_PNG) - ImageType.Jpeg -> clipData.description.hasMimeType(MIME_TYPE_IMAGE_JPEG) - ImageType.AnyExceptGif -> clipData.description.hasMimeType(MIME_TYPE_IMAGE_ALL) && - !clipData.description.hasMimeType(MIME_TYPE_IMAGE_GIF) - - ImageType.Gif -> clipData.description.hasMimeType(MIME_TYPE_IMAGE_GIF) - } - if (imageUri == null || !matchMimeType) { - // Image URI is null or the mime type doesn't match. - // This is not widely supported but some apps do store images as file paths in a text - - // Optional: Check if the clipboard item contains text that might be a file path - val text = clipboardItem.text ?: return null - if (!text.startsWith("file://")) { - return null - } - val fileUri = Uri.parse(text.toString()) - return try { - fileUri - } catch (e: Exception) { - e.printStackTrace() - null - } - } - return imageUri - } - - /** - * A method to see if any exceptions can occur - * before start reading the file. - * - * The app can lose access to the [Uri] due to lifecycle changes. - * - * @throws SecurityException When the app loses access to the [Uri] due to app lifecycle changes - * or app restart. - * @throws FileNotFoundException Could be thrown when the [Uri] is no longer on the clipboard. - * */ - @Throws(Exception::class) - private fun Uri.readOrThrow( - context: Context, - ) = try { - context.contentResolver.openInputStream(this)?.close() - } catch (e: Exception) { - throw e - } - - /** - * Get the clipboard Image. - * */ - fun getClipboardImage( - context: Context, - imageType: ImageType, - ): ByteArray? { - val primaryClipData = getPrimaryClip(context) ?: return null - - val imageUri = getImageUri( - clipData = primaryClipData, - imageType = imageType, - ) ?: return null - - try { - imageUri.readOrThrow(context) - } catch (e: Exception) { - when (e) { - is SecurityException -> throw FlutterError( - "FILE_READ_PERMISSION_DENIED", - "An image exists on the clipboard, but the app no longer " + - "has permission to access it. This may be due to the app's " + - "lifecycle or a recent app restart: ${e.message}", - e.toString(), - ) - - is FileNotFoundException -> throw FlutterError( - "FILE_NOT_FOUND", - "The image file can't be found, the provided URI could not be opened: ${e.message}", - e.toString() - ) - - else -> throw FlutterError( - "UNKNOWN_ERROR_READING_FILE", - "An unknown occurred while reading the image file URI: ${e.message}", - e.toString() - ) - } - } - val imageBytes = when (imageType) { - ImageType.Png, ImageType.Jpeg, - ImageType.AnyExceptGif -> getClipboardImageAsPng(context, imageUri) - - ImageType.Gif -> getClipboardGif(context, imageUri) - } - return imageBytes - } - - /** - * Get the image from [imageUri] and then convert it to [Bitmap] to decode and compress it - * to [ImageType.Png] - * */ - private fun getClipboardImageAsPng( - context: Context, - imageUri: Uri - ): ByteArray { - val bitmap = try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - // Api 29 and above (use a newer API) - val source = ImageDecoder.createSource(context.contentResolver, imageUri) - source.decodeBitmap { _, _ -> } - } else { - // Backward compatibility with older versions - checkNotNull(context.contentResolver.openInputStream(imageUri)) { - "Input stream is null, the provider might have recently crashed." - }.use { inputStream -> - val bitmap: Bitmap? = BitmapFactory.decodeStream(inputStream) - checkNotNull(bitmap) { "The image could not be decoded" } - bitmap - } - } - } catch (e: Exception) { - throw FlutterError( - "COULD_NOT_DECODE_IMAGE", - "Could not decode bitmap from Uri: ${e.message}", - e.toString(), - ) - } - - val imageBytes = ByteArrayOutputStream().use { outputStream -> - val compressedSuccessfully = - bitmap.compress( - Bitmap.CompressFormat.PNG, - /** - * Quality will be ignored for png images. See [Bitmap.CompressFormat.PNG] docs - * */ - 100, - outputStream - ) - if (!compressedSuccessfully) { - throw FlutterError( - "COULD_NOT_COMPRESS_IMAGE", - "Unknown error while compressing the image", - null, - ) - } - outputStream.toByteArray() - } - return imageBytes - } - - private fun getClipboardGif( - context: Context, - imageUri: Uri - ): ByteArray { - try { - val imageBytes = uriToByteArray(context, imageUri) - return imageBytes - } catch (e: Exception) { - throw FlutterError( - "COULD_NOT_CONVERT_URI_TO_BYTES", - "Could not convert Image URI to ByteArray: ${e.message}", - e.toString(), - ) - } - } - - private fun uriToByteArray(context: Context, uri: Uri): ByteArray { - return checkNotNull(context.contentResolver.openInputStream(uri)) { - "Input stream is null, the provider might have recently crashed." - }.use { inputStream -> - inputStream.readBytes() - } - } -} \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt deleted file mode 100644 index a7034cebc..000000000 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardRichTextHandler.kt +++ /dev/null @@ -1,54 +0,0 @@ -package dev.flutterquill.quill_native_bridge.clipboard - -import android.content.ClipData -import android.content.ClipDescription -import android.content.ClipboardManager -import android.content.Context -import dev.flutterquill.quill_native_bridge.generated.FlutterError - -object ClipboardRichTextHandler { - fun getClipboardHtml(context: Context): String? { - val clipboard = - context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - - if (!clipboard.hasPrimaryClip()) { - return null - } - - val primaryClipData = clipboard.primaryClip - - if (primaryClipData == null || primaryClipData.itemCount == 0) { - return null - } - - if (!primaryClipData.description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML)) { - return null - } - - val clipboardItem = primaryClipData.getItemAt(0) - - val htmlText = clipboardItem.htmlText ?: throw FlutterError( - "HTML_TEXT_NULL", - "Expected the HTML Text from the Clipboard to be not null" - ) - return htmlText - } - - fun copyHtmlToClipboard( - context: Context, - html: String, - ) { - try { - val clipboard = - context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newHtmlText("HTML", html, html) - clipboard.setPrimaryClip(clip) - } catch (e: Exception) { - throw FlutterError( - "COULD_NOT_COPY_HTML_TO_CLIPBOARD", - "Unknown error while copying the HTML to the clipboard: ${e.message}", - e.toString(), - ) - } - } -} \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt deleted file mode 100644 index b1574c0ca..000000000 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/clipboard/ClipboardWriteImageHandler.kt +++ /dev/null @@ -1,106 +0,0 @@ -package dev.flutterquill.quill_native_bridge.clipboard - -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.ImageDecoder -import android.os.Build -import androidx.core.content.FileProvider -import dev.flutterquill.quill_native_bridge.generated.FlutterError -import java.io.File - -object ClipboardWriteImageHandler { - fun copyImageToClipboard( - context: Context, - imageBytes: ByteArray, - ) { - val bitmap: Bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - // Api 29 and above (use a newer API) - try { - ImageDecoder.decodeBitmap(ImageDecoder.createSource(imageBytes)) - } catch (e: Exception) { - throw FlutterError( - "INVALID_IMAGE", - "The provided image bytes are invalid, image could not be decoded: ${e.message}", - e.toString(), - ) - } - } else { - // Backward compatibility with older versions - val bitmap: Bitmap = - BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) - ?: throw FlutterError( - "INVALID_IMAGE", - "The provided image bytes are invalid. Image could not be decoded.", - null, - ) - bitmap - } - - val tempImageFile = File(context.cacheDir, "temp_clipboard_image.png") - - try { - tempImageFile.outputStream().use { outputStream -> - val compressedSuccessfully = - bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) - if (!compressedSuccessfully) { - throw FlutterError( - "COULD_NOT_COMPRESS_IMAGE", - "Unknown error while compressing the image", - null, - ) - } - } - } catch (e: Exception) { - throw FlutterError( - "COULD_NOT_SAVE_TEMP_FILE", - "Unknown error while compressing and saving the temporary image file: ${e.message}", - e.toString(), - ) - } - - if (!tempImageFile.exists()) { - throw FlutterError( - "TEMP_FILE_NOT_FOUND", - "Recently created temporary file for copying the image to the clipboard is missing.", - null, - ) - } - - val authority = "${context.packageName}.fileprovider" - - val imageUri = try { - FileProvider.getUriForFile( - context, - authority, - tempImageFile, - ) - } catch (e: IllegalArgumentException) { - throw FlutterError( - "ANDROID_MANIFEST_NOT_CONFIGURED", - "You need to configure your AndroidManifest.xml file " + - "to register the provider with the meta-data with authority " + - authority, - e.toString(), - ) - } - - try { - val clipboard = - context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newUri(context.contentResolver, "Image", imageUri) - clipboard.setPrimaryClip(clip) - - // Don't delete the temporary image file, other apps will be unable to retrieve the image - // tempImageFile.delete() - } catch (e: Exception) { - throw FlutterError( - "COULD_NOT_COPY_IMAGE_TO_CLIPBOARD", - "Unknown error while copying the image to the clipboard: ${e.message}", - e.toString(), - ) - } - } -} \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt b/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt deleted file mode 100644 index ee6cad372..000000000 --- a/quill_native_bridge/quill_native_bridge_android/android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt +++ /dev/null @@ -1,155 +0,0 @@ -// Autogenerated from Pigeon (v22.4.1), do not edit directly. -// See also: https://pub.dev/packages/pigeon -@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") - -package dev.flutterquill.quill_native_bridge.generated - -import android.util.Log -import io.flutter.plugin.common.BasicMessageChannel -import io.flutter.plugin.common.BinaryMessenger -import io.flutter.plugin.common.MessageCodec -import io.flutter.plugin.common.StandardMessageCodec -import java.io.ByteArrayOutputStream -import java.nio.ByteBuffer - -private fun wrapResult(result: Any?): List { - return listOf(result) -} - -private fun wrapError(exception: Throwable): List { - return if (exception is FlutterError) { - listOf( - exception.code, - exception.message, - exception.details - ) - } else { - listOf( - exception.javaClass.simpleName, - exception.toString(), - "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) - ) - } -} - -/** - * Error class for passing custom error details to Flutter via a thrown PlatformException. - * @property code The error code. - * @property message The error message. - * @property details The error details. Must be a datatype supported by the api codec. - */ -class FlutterError ( - val code: String, - override val message: String? = null, - val details: Any? = null -) : Throwable() -private open class GeneratedMessagesPigeonCodec : StandardMessageCodec() { - override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { - return super.readValueOfType(type, buffer) - } - override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { - super.writeValue(stream, value) - } -} - -/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ -interface QuillNativeBridgeApi { - fun getClipboardHtml(): String? - fun copyHtmlToClipboard(html: String) - fun getClipboardImage(): ByteArray? - fun copyImageToClipboard(imageBytes: ByteArray) - fun getClipboardGif(): ByteArray? - - companion object { - /** The codec used by QuillNativeBridgeApi. */ - val codec: MessageCodec by lazy { - GeneratedMessagesPigeonCodec() - } - /** Sets up an instance of `QuillNativeBridgeApi` to handle messages through the `binaryMessenger`. */ - @JvmOverloads - fun setUp(binaryMessenger: BinaryMessenger, api: QuillNativeBridgeApi?, messageChannelSuffix: String = "") { - val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardHtml$separatedMessageChannelSuffix", codec) - if (api != null) { - channel.setMessageHandler { _, reply -> - val wrapped: List = try { - listOf(api.getClipboardHtml()) - } catch (exception: Throwable) { - wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyHtmlToClipboard$separatedMessageChannelSuffix", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val htmlArg = args[0] as String - val wrapped: List = try { - api.copyHtmlToClipboard(htmlArg) - listOf(null) - } catch (exception: Throwable) { - wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardImage$separatedMessageChannelSuffix", codec) - if (api != null) { - channel.setMessageHandler { _, reply -> - val wrapped: List = try { - listOf(api.getClipboardImage()) - } catch (exception: Throwable) { - wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyImageToClipboard$separatedMessageChannelSuffix", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val imageBytesArg = args[0] as ByteArray - val wrapped: List = try { - api.copyImageToClipboard(imageBytesArg) - listOf(null) - } catch (exception: Throwable) { - wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardGif$separatedMessageChannelSuffix", codec) - if (api != null) { - channel.setMessageHandler { _, reply -> - val wrapped: List = try { - listOf(api.getClipboardGif()) - } catch (exception: Throwable) { - wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } - } - } -} diff --git a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart b/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart deleted file mode 100644 index c96330f32..000000000 --- a/quill_native_bridge/quill_native_bridge_android/lib/quill_native_bridge_android.dart +++ /dev/null @@ -1,136 +0,0 @@ -// This file is referenced by pubspec.yaml. If you plan on moving this file -// Make sure to update pubspec.yaml to the new location. - -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; -import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; - -import 'src/messages.g.dart'; - -/// An implementation of [QuillNativeBridgePlatform] for Android. -/// -/// **Highly Experimental** and can be removed. -/// -/// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: -/// -/// ```console -/// Assertion failed: "Platform interfaces must not be implemented with `implements`" -/// ``` -/// -/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) -/// and [QuillNativeBridgePlatform] for more details. -class QuillNativeBridgeAndroid extends QuillNativeBridgePlatform { - QuillNativeBridgeAndroid._({ - @visibleForTesting QuillNativeBridgeApi? api, - }) : _hostApi = api ?? QuillNativeBridgeApi(); - - final QuillNativeBridgeApi _hostApi; - - /// Registers this class as the default instance of [QuillNativeBridgePlatform]. - static void registerWith() { - assert( - defaultTargetPlatform == TargetPlatform.android && !kIsWeb, - '$QuillNativeBridgeAndroid should be only used for Android.', - ); - QuillNativeBridgePlatform.instance = QuillNativeBridgeAndroid._(); - } - - @override - Future isSupported(QuillNativeBridgeFeature feature) async => { - QuillNativeBridgeFeature.getClipboardHtml, - QuillNativeBridgeFeature.copyHtmlToClipboard, - QuillNativeBridgeFeature.copyImageToClipboard, - QuillNativeBridgeFeature.getClipboardImage, - QuillNativeBridgeFeature.getClipboardGif, - }.contains(feature); - - @override - Future getClipboardHtml() async => _hostApi.getClipboardHtml(); - - @override - Future copyHtmlToClipboard(String html) => - _hostApi.copyHtmlToClipboard(html); - - @override - Future getClipboardImage() async { - try { - return await _hostApi.getClipboardImage(); - } on PlatformException catch (e) { - if (kDebugMode && - (e.code == 'FILE_READ_PERMISSION_DENIED' || - e.code == 'FILE_NOT_FOUND')) { - _printAndroidClipboardImageAccessKnownIssue(e); - return null; - } - rethrow; - } - } - - @override - Future copyImageToClipboard(Uint8List imageBytes) async { - try { - await _hostApi.copyImageToClipboard(imageBytes); - } on PlatformException catch (e) { - // TODO: Update the link, issue and related info if this plugin - // moved outside of flutter-quill repo - if (kDebugMode && e.code == 'ANDROID_MANIFEST_NOT_CONFIGURED') { - debugPrint( - 'It looks like your AndroidManifest.xml is not configured properly ' - 'to support copying images to the clipboard on Android.\n' - "If you're interested in this feature, refer to https://github.com/singerdmx/flutter-quill#-platform-specific-configurations\n" - 'This message will only shown in debug mode.\n' - 'Platform details: ${e.toString()}', - ); - throw AssertionError( - 'Optional AndroidManifest configuration is missing. ' - 'Copying images to the clipboard on Android require modifying `AndroidManifest.xml`. ' - 'A message was shown above this error for more details. This' - 'error will only arise in debug mode.', - ); - } - rethrow; - } - } - - @override - Future getClipboardGif() async { - try { - return await _hostApi.getClipboardGif(); - } on PlatformException catch (e) { - if (kDebugMode && - (e.code == 'FILE_READ_PERMISSION_DENIED' || - e.code == 'FILE_NOT_FOUND')) { - _printAndroidClipboardImageAccessKnownIssue(e); - return null; - } - rethrow; - } - } - - /// Should be only used internally for [getClipboardGif] and [getClipboardImage] - /// for **Android only**. - /// - /// This issue can be caused by `SecurityException` or `FileNotFoundException` - /// from Android side. - /// - /// See [#2243](https://github.com/singerdmx/flutter-quill/issues/2243) for more details. - void _printAndroidClipboardImageAccessKnownIssue(PlatformException e) { - assert( - defaultTargetPlatform == TargetPlatform.android, - '_printAndroidClipboardImageAccessKnownIssue() should be only used for Android.', - ); - assert( - kDebugMode, - '_printAndroidClipboardImageAccessKnownIssue() should be only called in debug mode', - ); - if (kDebugMode) { - debugPrint( - 'Could not retrieve the image from clipbaord as the app no longer have access to the image.\n' - 'This can happen on app restart or lifecycle changes.\n' - 'This is known issue on Android and this message will be only shown in debug mode.\n' - 'Refer to https://github.com/singerdmx/flutter-quill/issues/2243 for discussion.\n' - 'Platform details: ${e.toString()}', - ); - } - } -} diff --git a/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart deleted file mode 100644 index c7327cb76..000000000 --- a/quill_native_bridge/quill_native_bridge_android/lib/src/messages.g.dart +++ /dev/null @@ -1,175 +0,0 @@ -// Autogenerated from Pigeon (v22.4.1), do not edit directly. -// See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers - -import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; - -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; -import 'package:flutter/services.dart'; - -PlatformException _createConnectionError(String channelName) { - return PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel: "$channelName".', - ); -} - -class _PigeonCodec extends StandardMessageCodec { - const _PigeonCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is int) { - buffer.putUint8(4); - // TODO: Workaround to https://github.com/flutter/packages/pull/7735 - // ignore: cascade_invocations - buffer.putInt64(value); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - default: - return super.readValueOfType(type, buffer); - } - } -} - -class QuillNativeBridgeApi { - /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is - /// available for dependency injection. If it is left null, the default - /// BinaryMessenger will be used which routes to the host platform. - QuillNativeBridgeApi( - {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) - : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; - final BinaryMessenger? pigeonVar_binaryMessenger; - - static const MessageCodec pigeonChannelCodec = _PigeonCodec(); - - final String pigeonVar_messageChannelSuffix; - - Future getClipboardHtml() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as String?); - } - } - - Future copyHtmlToClipboard(String html) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send([html]) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - - Future getClipboardImage() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as Uint8List?); - } - } - - Future copyImageToClipboard(Uint8List imageBytes) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send([imageBytes]) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - - Future getClipboardGif() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_android.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as Uint8List?); - } - } -} diff --git a/quill_native_bridge/quill_native_bridge_android/pigeons/messages.dart b/quill_native_bridge/quill_native_bridge_android/pigeons/messages.dart deleted file mode 100644 index cd84dedf0..000000000 --- a/quill_native_bridge/quill_native_bridge_android/pigeons/messages.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:pigeon/pigeon.dart'; - -@ConfigurePigeon(PigeonOptions( - dartOut: 'lib/src/messages.g.dart', - // Using `GeneratedMessages.kt` instead of `Messages.g.kt` to follow - // Kotlin conventions: https://kotlinlang.org/docs/coding-conventions.html#source-file-names - kotlinOut: - 'android/src/main/kotlin/dev/flutterquill/quill_native_bridge/generated/GeneratedMessages.kt', - kotlinOptions: KotlinOptions( - package: 'dev.flutterquill.quill_native_bridge.generated', - ), - dartPackageName: 'quill_native_bridge_android', -)) -@HostApi() -abstract class QuillNativeBridgeApi { - // HTML - String? getClipboardHtml(); - void copyHtmlToClipboard(String html); - - // Image - Uint8List? getClipboardImage(); - void copyImageToClipboard(Uint8List imageBytes); - Uint8List? getClipboardGif(); -} diff --git a/quill_native_bridge/quill_native_bridge_android/pubspec.yaml b/quill_native_bridge/quill_native_bridge_android/pubspec.yaml deleted file mode 100644 index 0a3419388..000000000 --- a/quill_native_bridge/quill_native_bridge_android/pubspec.yaml +++ /dev/null @@ -1,31 +0,0 @@ -name: quill_native_bridge_android -description: "Android implementation of the quill_native_bridge plugin." -version: 0.0.1-dev.1 -homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_android -repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_android -issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ -documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_android - -environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' - -dependencies: - flutter: - sdk: flutter - quill_native_bridge_platform_interface: ^0.0.1-dev.2 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^4.0.0 - pigeon: ^22.4.1 - -flutter: - plugin: - implements: quill_native_bridge - platforms: - android: - package: dev.flutterquill.quill_native_bridge - pluginClass: QuillNativeBridgePlugin - dartPluginClass: QuillNativeBridgeAndroid diff --git a/quill_native_bridge/quill_native_bridge_android/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_android/pubspec_overrides.yaml deleted file mode 100644 index a07f95b27..000000000 --- a/quill_native_bridge/quill_native_bridge_android/pubspec_overrides.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing -dependency_overrides: - quill_native_bridge_platform_interface: - path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_ios/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_ios/CHANGELOG.md deleted file mode 100644 index 13b5ca517..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/CHANGELOG.md +++ /dev/null @@ -1,11 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -## 0.0.1-dev.1 - -- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. - -## 0.0.1-dev.0 - -- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_ios/LICENSE b/quill_native_bridge/quill_native_bridge_ios/LICENSE deleted file mode 100644 index e7ff73e1b..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_ios/README.md b/quill_native_bridge/quill_native_bridge_ios/README.md deleted file mode 100644 index 70b079aff..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# đŸĒļ Quill Native Bridge - -The iOS implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). - -## ⚙ī¸ Usage - -This package is endorsed, which means you can simply use `quill_native_bridge` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. - -However, if you import this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. - -## 📉 Note on breaking changes - -The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_ios/ios/.gitignore b/quill_native_bridge/quill_native_bridge_ios/ios/.gitignore deleted file mode 100644 index 034771fc9..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/ios/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -.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/quill_native_bridge/quill_native_bridge_ios/ios/Assets/.gitkeep b/quill_native_bridge/quill_native_bridge_ios/ios/Assets/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/quill_native_bridge/quill_native_bridge_ios/ios/Classes/Messages.g.swift b/quill_native_bridge/quill_native_bridge_ios/ios/Classes/Messages.g.swift deleted file mode 100644 index ff9d43de5..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/ios/Classes/Messages.g.swift +++ /dev/null @@ -1,186 +0,0 @@ -// Autogenerated from Pigeon (v22.4.0), do not edit directly. -// See also: https://pub.dev/packages/pigeon - -import Foundation - -#if os(iOS) - import Flutter -#elseif os(macOS) - import FlutterMacOS -#else - #error("Unsupported platform.") -#endif - -/// Error class for passing custom error details to Dart side. -final class PigeonError: Error { - let code: String - let message: String? - let details: Any? - - init(code: String, message: String?, details: Any?) { - self.code = code - self.message = message - self.details = details - } - - var localizedDescription: String { - return - "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" - } -} - -private func wrapResult(_ result: Any?) -> [Any?] { - return [result] -} - -private func wrapError(_ error: Any) -> [Any?] { - if let pigeonError = error as? PigeonError { - return [ - pigeonError.code, - pigeonError.message, - pigeonError.details, - ] - } - if let flutterError = error as? FlutterError { - return [ - flutterError.code, - flutterError.message, - flutterError.details, - ] - } - return [ - "\(error)", - "\(type(of: error))", - "Stacktrace: \(Thread.callStackSymbols)", - ] -} - -private func isNullish(_ value: Any?) -> Bool { - return value is NSNull || value == nil -} - -private func nilOrValue(_ value: Any?) -> T? { - if value is NSNull { return nil } - return value as! T? -} - -private class MessagesPigeonCodecReader: FlutterStandardReader { -} - -private class MessagesPigeonCodecWriter: FlutterStandardWriter { -} - -private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { - override func reader(with data: Data) -> FlutterStandardReader { - return MessagesPigeonCodecReader(data: data) - } - - override func writer(with data: NSMutableData) -> FlutterStandardWriter { - return MessagesPigeonCodecWriter(data: data) - } -} - -class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { - static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter()) -} - -/// Generated protocol from Pigeon that represents a handler of messages from Flutter. -protocol QuillNativeBridgeApi { - func isIosSimulator() throws -> Bool - func getClipboardHtml() throws -> String? - func copyHtmlToClipboard(html: String) throws - func getClipboardImage() throws -> FlutterStandardTypedData? - func copyImageToClipboard(imageBytes: FlutterStandardTypedData) throws - func getClipboardGif() throws -> FlutterStandardTypedData? -} - -/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. -class QuillNativeBridgeApiSetup { - static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared } - /// Sets up an instance of `QuillNativeBridgeApi` to handle messages through the `binaryMessenger`. - static func setUp(binaryMessenger: FlutterBinaryMessenger, api: QuillNativeBridgeApi?, messageChannelSuffix: String = "") { - let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" - let isIosSimulatorChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.isIosSimulator\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - isIosSimulatorChannel.setMessageHandler { _, reply in - do { - let result = try api.isIosSimulator() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) - } - } - } else { - isIosSimulatorChannel.setMessageHandler(nil) - } - let getClipboardHtmlChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardHtml\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - getClipboardHtmlChannel.setMessageHandler { _, reply in - do { - let result = try api.getClipboardHtml() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) - } - } - } else { - getClipboardHtmlChannel.setMessageHandler(nil) - } - let copyHtmlToClipboardChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.copyHtmlToClipboard\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - copyHtmlToClipboardChannel.setMessageHandler { message, reply in - let args = message as! [Any?] - let htmlArg = args[0] as! String - do { - try api.copyHtmlToClipboard(html: htmlArg) - reply(wrapResult(nil)) - } catch { - reply(wrapError(error)) - } - } - } else { - copyHtmlToClipboardChannel.setMessageHandler(nil) - } - let getClipboardImageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardImage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - getClipboardImageChannel.setMessageHandler { _, reply in - do { - let result = try api.getClipboardImage() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) - } - } - } else { - getClipboardImageChannel.setMessageHandler(nil) - } - let copyImageToClipboardChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.copyImageToClipboard\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - copyImageToClipboardChannel.setMessageHandler { message, reply in - let args = message as! [Any?] - let imageBytesArg = args[0] as! FlutterStandardTypedData - do { - try api.copyImageToClipboard(imageBytes: imageBytesArg) - reply(wrapResult(nil)) - } catch { - reply(wrapError(error)) - } - } - } else { - copyImageToClipboardChannel.setMessageHandler(nil) - } - let getClipboardGifChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardGif\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - getClipboardGifChannel.setMessageHandler { _, reply in - do { - let result = try api.getClipboardGif() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) - } - } - } else { - getClipboardGifChannel.setMessageHandler(nil) - } - } -} diff --git a/quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgeImpl.swift b/quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgeImpl.swift deleted file mode 100644 index 755d5c479..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgeImpl.swift +++ /dev/null @@ -1,47 +0,0 @@ -import Foundation -import Flutter - -class QuillNativeBridgeImpl: QuillNativeBridgeApi { - func isIosSimulator() throws -> Bool { -#if targetEnvironment(simulator) - return true -#else - return false -#endif - } - - func getClipboardHtml() throws -> String? { - guard let htmlData = UIPasteboard.general.data(forPasteboardType: "public.html") else { - return nil - } - let html = String(data: htmlData, encoding: .utf8) - return html - } - - func copyHtmlToClipboard(html: String) throws { - UIPasteboard.general.setValue(html, forPasteboardType: "public.html") - } - - func copyImageToClipboard(imageBytes: FlutterStandardTypedData) throws { - guard let image = UIImage(data: imageBytes.data) else { - throw PigeonError(code: "INVALID_IMAGE", message: "Unable to create UIImage from image bytes.", details: nil) - } - UIPasteboard.general.image = image - } - - func getClipboardImage() throws -> FlutterStandardTypedData? { - let image = UIPasteboard.general.image - guard let data = image?.pngData() else { - return nil - } - return FlutterStandardTypedData(bytes: data) - } - - func getClipboardGif() throws -> FlutterStandardTypedData? { - guard let data = UIPasteboard.general.data(forPasteboardType: "com.compuserve.gif") else { - return nil - } - return FlutterStandardTypedData(bytes: data) - - } -} diff --git a/quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgePlugin.swift deleted file mode 100644 index d77cff7ef..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/ios/Classes/QuillNativeBridgePlugin.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Flutter -import UIKit - -public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let messenger = registrar.messenger() - let api = QuillNativeBridgeImpl() - QuillNativeBridgeApiSetup.setUp(binaryMessenger: messenger, api: api) - } -} diff --git a/quill_native_bridge/quill_native_bridge_ios/ios/Resources/PrivacyInfo.xcprivacy b/quill_native_bridge/quill_native_bridge_ios/ios/Resources/PrivacyInfo.xcprivacy deleted file mode 100644 index a34b7e2e6..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/ios/Resources/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,14 +0,0 @@ - - - - - NSPrivacyTrackingDomains - - NSPrivacyAccessedAPITypes - - NSPrivacyCollectedDataTypes - - NSPrivacyTracking - - - diff --git a/quill_native_bridge/quill_native_bridge_ios/ios/quill_native_bridge_ios.podspec b/quill_native_bridge/quill_native_bridge_ios/ios/quill_native_bridge_ios.podspec deleted file mode 100644 index 6690fa6c9..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/ios/quill_native_bridge_ios.podspec +++ /dev/null @@ -1,29 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint quill_native_bridge_ios.podspec` to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'quill_native_bridge_ios' - s.version = '0.0.1' - s.summary = 'A plugin for flutter_quill' - s.description = <<-DESC -An internal plugin for flutter_quill package to access platform-specific APIs. - DESC - s.homepage = 'https://github.com/singerdmx/flutter-quill' - s.license = { :file => '../LICENSE' } - s.author = { 'Flutter Quill' => 'https://github.com/singerdmx/flutter-quill' } - 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 = {'quill_native_bridge_ios_privacy' => ['Resources/PrivacyInfo.xcprivacy']} -end diff --git a/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart b/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart deleted file mode 100644 index d98eb5382..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/lib/quill_native_bridge_ios.dart +++ /dev/null @@ -1,66 +0,0 @@ -// This file is referenced by pubspec.yaml. If you plan on moving this file -// Make sure to update pubspec.yaml to the new location. - -import 'package:flutter/foundation.dart'; -import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; - -import 'src/messages.g.dart'; - -/// An implementation of [QuillNativeBridgePlatform] for iOS. -/// -/// **Highly Experimental** and can be removed. -/// -/// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: -/// -/// ```console -/// Assertion failed: "Platform interfaces must not be implemented with `implements`" -/// ``` -/// -/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) -/// and [QuillNativeBridgePlatform] for more details. -class QuillNativeBridgeIos extends QuillNativeBridgePlatform { - QuillNativeBridgeIos._({ - @visibleForTesting QuillNativeBridgeApi? api, - }) : _hostApi = api ?? QuillNativeBridgeApi(); - - final QuillNativeBridgeApi _hostApi; - - /// Registers this class as the default instance of [QuillNativeBridgePlatform]. - static void registerWith() { - assert( - defaultTargetPlatform == TargetPlatform.iOS && !kIsWeb, - '$QuillNativeBridgeIos should be only used for iOS.', - ); - QuillNativeBridgePlatform.instance = QuillNativeBridgeIos._(); - } - - @override - Future isSupported(QuillNativeBridgeFeature feature) async => { - QuillNativeBridgeFeature.isIOSSimulator, - QuillNativeBridgeFeature.getClipboardHtml, - QuillNativeBridgeFeature.copyHtmlToClipboard, - QuillNativeBridgeFeature.copyImageToClipboard, - QuillNativeBridgeFeature.getClipboardImage, - QuillNativeBridgeFeature.getClipboardGif, - }.contains(feature); - - @override - Future isIOSSimulator() => _hostApi.isIosSimulator(); - - @override - Future getClipboardHtml() => _hostApi.getClipboardHtml(); - - @override - Future copyHtmlToClipboard(String html) => - _hostApi.copyHtmlToClipboard(html); - - @override - Future getClipboardImage() => _hostApi.getClipboardImage(); - - @override - Future copyImageToClipboard(Uint8List imageBytes) => - _hostApi.copyImageToClipboard(imageBytes); - - @override - Future getClipboardGif() => _hostApi.getClipboardGif(); -} diff --git a/quill_native_bridge/quill_native_bridge_ios/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_ios/lib/src/messages.g.dart deleted file mode 100644 index e12dd5f17..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/lib/src/messages.g.dart +++ /dev/null @@ -1,185 +0,0 @@ -// Autogenerated from Pigeon (v22.4.0), do not edit directly. -// See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers - -import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; - -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; -import 'package:flutter/services.dart'; - -PlatformException _createConnectionError(String channelName) { - return PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel: "$channelName".', - ); -} - -class _PigeonCodec extends StandardMessageCodec { - const _PigeonCodec(); -} - -class QuillNativeBridgeApi { - /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is - /// available for dependency injection. If it is left null, the default - /// BinaryMessenger will be used which routes to the host platform. - QuillNativeBridgeApi( - {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) - : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; - final BinaryMessenger? pigeonVar_binaryMessenger; - - static const MessageCodec pigeonChannelCodec = _PigeonCodec(); - - final String pigeonVar_messageChannelSuffix; - - Future isIosSimulator() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.isIosSimulator$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as bool?)!; - } - } - - Future getClipboardHtml() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as String?); - } - } - - Future copyHtmlToClipboard(String html) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send([html]) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - - Future getClipboardImage() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as Uint8List?); - } - } - - Future copyImageToClipboard(Uint8List imageBytes) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send([imageBytes]) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - - Future getClipboardGif() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_ios.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as Uint8List?); - } - } -} diff --git a/quill_native_bridge/quill_native_bridge_ios/pigeons/messages.dart b/quill_native_bridge/quill_native_bridge_ios/pigeons/messages.dart deleted file mode 100644 index 3f0df4f1a..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/pigeons/messages.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:pigeon/pigeon.dart'; - -@ConfigurePigeon(PigeonOptions( - dartOut: 'lib/src/messages.g.dart', - swiftOut: 'ios/Classes/Messages.g.swift', - dartPackageName: 'quill_native_bridge_ios', -)) -@HostApi() -abstract class QuillNativeBridgeApi { - bool isIosSimulator(); - - // HTML - String? getClipboardHtml(); - void copyHtmlToClipboard(String html); - - // Image - Uint8List? getClipboardImage(); - void copyImageToClipboard(Uint8List imageBytes); - Uint8List? getClipboardGif(); -} diff --git a/quill_native_bridge/quill_native_bridge_ios/pubspec.yaml b/quill_native_bridge/quill_native_bridge_ios/pubspec.yaml deleted file mode 100644 index 3007ad345..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/pubspec.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: quill_native_bridge_ios -description: "iOS implementation of the quill_native_bridge plugin." -version: 0.0.1-dev.1 -homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_ios -repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_ios -issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ -documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_ios - -environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' - -dependencies: - flutter: - sdk: flutter - quill_native_bridge_platform_interface: ^0.0.1-dev.2 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^4.0.0 - pigeon: ^22.4.0 - -flutter: - plugin: - implements: quill_native_bridge - platforms: - ios: - pluginClass: QuillNativeBridgePlugin - dartPluginClass: QuillNativeBridgeIos diff --git a/quill_native_bridge/quill_native_bridge_ios/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_ios/pubspec_overrides.yaml deleted file mode 100644 index a07f95b27..000000000 --- a/quill_native_bridge/quill_native_bridge_ios/pubspec_overrides.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing -dependency_overrides: - quill_native_bridge_platform_interface: - path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_linux/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_linux/CHANGELOG.md deleted file mode 100644 index 13b5ca517..000000000 --- a/quill_native_bridge/quill_native_bridge_linux/CHANGELOG.md +++ /dev/null @@ -1,11 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -## 0.0.1-dev.1 - -- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. - -## 0.0.1-dev.0 - -- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_linux/LICENSE b/quill_native_bridge/quill_native_bridge_linux/LICENSE deleted file mode 100644 index e7ff73e1b..000000000 --- a/quill_native_bridge/quill_native_bridge_linux/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_linux/README.md b/quill_native_bridge/quill_native_bridge_linux/README.md deleted file mode 100644 index cd731524d..000000000 --- a/quill_native_bridge/quill_native_bridge_linux/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# đŸĒļ Quill Native Bridge - -The Linux implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). - -## ⚙ī¸ Usage - -This package is endorsed, which means you can simply use `quill_native_bridge` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. - -However, if you import this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. - -## 📉 Note on breaking changes - -The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_linux/assets/xclip b/quill_native_bridge/quill_native_bridge_linux/assets/xclip deleted file mode 100755 index e1b401e268cb7001e35d2932c3ed1dae9de2672c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30896 zcmeHwdwf*Yz3-kRBN53=6l0?(Y;n*~Nl1iX0t{wCLUwe3kOu@s!jMeJ=wv2NW)Li5 zbP{DcjN(0g*jsJswYIdiE%tE^rBcH~BDPlaXiI$@v5M{y(HI{mRweiQTaTU1z?|EA zd(QpmWb@hcTfg^j{nm4@8Fucdb}!1XSQL3>Dhm|Cisx}kgH+gc8 z+|qn;{)DboF)6hF$U&Wnz-37v#+VSgY=)pAcJex-T=``~9q=|D= z8u}e+>|74{bCrvfiXH@jORkgefS#*ds1)=tvhndSN+iEV(53trO#UJzH)@Eo!nZ-6 zdMb%wI^rQabv>LZ?R+5Yr22gfVkF;{BEKB+MDIzVD=oE+i(A^fA@4e0ci05>Ka>yE(HHh^_Di+Jd)^dO!@_Uc*JRt7W0PwytuitG-HU z0oK&GsH~--vA%Xmjndp1G5cH?46H|#4G6h4><@TJJCX&2({kZ9gKupVW1%# z40$DQVTUKM&Xl7D_%y6jEVY5I2wa6a>pY=u@4{ebrzg;+G^a)t>Z}d;!ak3m#ker! z^@P0*zRoVcm*+_74u@L1HY(l?zOd4c76~Y5l~6DQBy=;H11-^db4zQ-%`NR7pI?Ci zzdzUtwKiXXl*2*4((VnmQ`rygmKLZ7f-Mm8u!OaCwE03Hw|9knfp9ySBJCieZ$}kc zl}>LbMbO?#jihu+`yuEn>(EgG>q#C~mG)ahzOYwmM-Xj_uN6(x!j)KAP*@#WT3W*! zySy!kE_kaqMA0CG)(xJPc3*%egDT$H1uAx6t)jzqdQd5t_bMQwC(u4@4l9?G80A)8 zYVo#t!XD_a?e6C4N(&lEsd3jMt|}_!qcH<}G7K!^lkd@p zM*B5>jmi+0_I9KpFRsrmNK5Q_Lk{>F3j#>WP>j8BX@8*?N!b7J8GimaS`^Zo9Gvs=z}JDk%>NRqKi-%SI*bullI2) zlJhCi<$jrXa=s>dIT6Gq=To9DkVHT^pA!8V6J6WP5qgZH_L5hfiEi!(%_h1?JyTg@ zqEp|ISBHsie*Wn)(d8o~>1;63Wt$Pb$3#cN8`l;SeY`}W2z_gB-=;xd495vA| zFwu1r{YxhLpoyMuq7R$s7n$ga=(p5XlT7q%6W#ofmTRKZvxB_yP4vl<2&kIqQ%v*% z6J0gYi%s-PP4qGoeX5CGVWMAVqH89)!$hw$(WjZ{%_jQgCi)r^{R$Jk!$dDI(Ys9a z=_dLH6aC93dXI^IrHQ`9M4w@zZ!^&gP4ov$^qD65b`!nWMBibemzd}~P4ufw^j#)8 zJ&Vb!-$b7+iGWX;=wh*A+`}gN9FzQ5aV!I485qmJSO&&2FqVO_42)%9ECXX1`0ty6 z5ABoxrbXY)*5cN`v?z+!*B{OpIjltwWbbE587Vz)wW17-Oq&Q`pQ4a_2XTgyuaAt3 zY-2bbd<-R@GjKZa7)tIna60H1N;B-JS zl>FeNtS=o*p#BC<2NI~ifzv?*>Tlq50D<}&I2}Bo{svA5VyM4?(*XqPZ{T$BfchIa z9XO!=22KZOsK0^J!2{}V;B?@C`WrYMG@$+lP6rIAzk$=i0_tzzQyBii7qb2;!(TUW zIzT}E4V(@RP=5ob0|V6G!0Dg>^*3-jAVB>MoDK$1e*>oj0o32X=^z00H*h)tK>ZDz z9{f>%1E&Xm)Zf7AK_B%uaC*Q;{SBNB98rG*rw4ts|L3y)^nj1{H*k8eNBt#^(eltL z8ZA`{8!y0Lz5Z1E#T5LR6#U5)d`}Ag>lFN_DflBP_(Li9*HiFB3f`B3-=2a;Qt-|c z+?#^mn1VN@;EPl6>J;3Wf|sV?g(>*76#SADJTC>$Nx`it_!lcrZNHCG@DnNcu@wB( z6#T^${FxN|$rOA~3jXU9{HH1SBPsYpDfrh@@I(sUmxAA(f=5#D&J^66g5Q{eH<@wQ z3fFR1ldG{Q_Jx*kR$=rtXt58qJKrtC!qTS2v!>C2*J8bn0uW02^>LUWA-MwMQj1wv zqOJ6gFo%L_(53%@%Y6(va2Js3Z#d}{%mM?}vQgFPT(wB5 z-p}P!?)5TK-ONdkGF9qh!mEv|PL!(4xm*$VnoX(;IcW>3Hek&Vf(YqDWJbT7GgrYb zNP|S0$Vq?Xq+>)ny zlj6+8`cV_>hk9Qk8sjTB7KB47S&yZr2_3gYNCpmf9A|mF7G1J0Zv-W zNppzw7$;o-(tx#i)WoI4{2FI|hNT3#m`tQQIcW+vF_B0CPTCEr0c*~viIe!Xo4$-Q z_i_`T5orM@P3E~6B+`|f^e{8=4k)5gUnZuFGq-RPM~L(pHXEpT1~>5xk#tVFiJ2Gx zMfA#DC}J$^0ia9o=W@T|)_y^%Kjow$tjGh_M};6N{17p>ff@5VnkC-<>BxwdsB$#d z#{Qxua+ACz0_Cdv7DJ*A6gIuZ<*=$hnDuyjLid ziPB>L`qEEW{;${nguH6;ycHx8S2X+Y&WmSliw<|$dk>wBMBTR;EgBtOYwx9w6di7j zT%^TIAFC&6=e1B`?T>I)gA{dM!=pGCs^CurA5nZr(utfm8OZw=9>pGt;?Tzwg}wKB zJZ43m8C?07T=@iv<8iR$P|5SI-%!7D(J#gnt@WOx1oj>sr)A!wQ!(C^dy_){^a9FF z+{xmL9#D5HN(C}_ur_u;e+D(KiWmN1nW9wpov`;_fJZ+qp7{hlLAmzOXUWU%M5zta zHk&K-M{}sj>nV)blUl2_3MKoL3Z?IPdtV3UG5GlwjA=3FRB)pGmYDTY6pk0#r}V%Y z_&H|-cgNn*5}Phje#e5J{*wOYM=Zt@q-)u)|C;3>QtmE49{GbiF=G;XyF2!t-U;_` ze0*1QxZB?MJv;$n!u+U?{|d?kFo*7tJQ z-m~eFk?~q$;fTH0itP41ADIz#-p2$kLzw$u6<)R2ODsse9;rg@egd+-oJCuD2Z_X; z%@lO>

}FJ?IW4hupEh>bRABTSG@+Kn62N%^OrlZ$+1R1`QgY2n&-F3=s=Yt zo092H6z=4icPFO(3?+N-cgQx_RdMSc7>(){S9$*(XIG<>-4}*V)cFwPl+CTK=&)t; z+cL(nu)1l&yozyKzqH8(UrrMb9YC_U546RTZ`4!to{(l2#>z^$#+@j`NmKxaK zAB28YF52tP1Ba21DB9){d*2|s=YaJBYHbkXrRSs6_X($0mz;p7$?&94XPt>}p4+r5 z`EJ_YH-kp}od;--T@}-BxY4!Bb)##wt7X5K_wt@Y(C*kLT73RK-HVQZ`G)GD#Rk|6 z_^_2)c0ZDxfpPjEIQBk29)U5heUrrEP8EO;!1#oH=xTjqS1fjHkuhZ`Q<3~e{EWCM_a{v9Uq}W zvI`7nhU;h+|1sJRxt@*OQKz5q0kwd08qlPIDQUoZ5&8i2kL33;O%GVVggEF*u41SH zE*;k;-(u5G=_;_He+)q1&TN%_a2-hHgZ94nA%|Ar%@cR-CD8%vAHk*rA~rilX_iR7 z&v$wcKmaR|kK__L*E5h;gTo;V#Ki#mHShD4=v7vSQjH|yPLeNqUSB&%Gh8d09vcX& zA71Qz*JFQ$%ueBg{S;Ut&kj>^2DttoiWz1I(B6PhwMyk?fAW?vDM9)yf_Fcl}AK zY1H{eE!tK?n8o(qw_rnGd;&Fln3{LM`YJIISMoNNsCjywokA*RtmX>g}epLKuvo;>|pX{MsnT-vU?(|iUd37t5BE&h2)JSfh3lV=Iug6 z!t$2+zg#k;!vSCVhxne}kA&=Oi$(*p#Ajz;b|LKW8N@ zURnVRA^!zH{~C$Uv!2kX@vh_2uK~fLJc&zR1+gc_!;M}*Nea<;8nEty3^iV|i7)RH z$YvsMGmcnApVNIXojk_(ClT^BY=iuxENwZjKz}!#|H^!Xp{ZX^DqrOiyGa7Q(2(#@ z^|DCfO7hv7r;yLM)xyAp!x}mo9?J4=fu$$V1u%F-|9*`eKZ(&Jmgp9khG2m@I=~*M zx_y)Kdnopj7JCotj9&0AX!r7=IU_Zi{S?i5(wa$|-q`-+hlr{+`u6Z*%Sm^_c{>iD zkIzDyL*y(zON;K$*6)TzckFH1BWWub+lv9V0Cs4A-F+PV@(&|VC0F2(7H$0N8YJN= zQxYDfG>uSTXHmCFJJN^rJtS>T*>aM8j--E&;PfkCKDmeO(Q|Gf*$F1g8UofI zA=$rh-{mAh4KJH$5=lf!;zwNKOC)i&Nn$cdct~PDm-u*5ikJ6sMz3E(68{7VSI=h4 zNqgT4Hl>v2;yfSQv@SC8AVerV(P0eet7#gEA-M-L^of+BSlCfPi=;;51|Rz|znr9U z9j%{rRLZAPg*p6fAtAD^LbNMf=q8yBfW{xL6TS2jADdEH{K=|6#8Ew;*`F%>uV zeYsk^(UI?teL%Ze^sQ`t1q^7|n}>g;&4XkZ%+n36WrkkU|E80{_x0!BL1njKKvUn) z5`|ll0d$e$7i#fywAec=0Q8BHe%3=~Q}xgT{+ev!i_I)#5dD!=NnZq8=u+RUHX0?b z80OeSjXI(yeFO;#KHD7|(62_kZuI&5_$WTVHF{^CNKm)-y+c`wZ_7D%!0yGl;f z#-3ZPVM7^xbFi)vOWd3sYLT2L&`;1X1&D1V@2)CE`Dh!^y(nl2>uv(&!{Iy(#nn{C z8OPZpN5%65c3nN_Af$djlP~=$f$3eKgeJVcKJnZ|y zzLD^_{oGG-5_)?3=>CfFqwnVH4^uOuN;Z6Vm`}pJR2_Imj`g?D`8CQmXO60~eByyK zU*G>9>HpwA)W^xOhNELVw(3sQO-pRB=;y;69zZFgi_tC6arDRD0xxw z`uM9xyi^}Lk6s-6qpPVl_OYw6HueIUZ-D&{4V$nvv@FNmj$AiYO^ZM1=wOsAM|`Ve zJF09tvBn8QNq=qJf_A7}D6)|shAHQjgoeA~nbjpDcq+2@ZUY`2#^Uo13bcs*FOo;j znoWE5sac>!4|G(;hELhBS75`09y{wi>}A{zRll6l5Op4`gbnm-6|3xilq1`i*aZ;7 zh+~qUBPj?$2z>)W@l1-RuM2%3`soyV-v$)4=;Mwfu!ZsPTU2%+la`I-YU0GR99pyt zj1XlZo^u0CRF@oD9MiGkrkNoZuHZr6hnNO3A*3XyjrOnTrx)A%@_<*xxr=J-hx!(g zEt;2Lsk)@UW7a){LBQ#5F&|g5FQ1B!B#qM~Uqb@p} z9lkI+oNMpxgT30lZ)W1$gPKrv+pGV|(h@J$=yB{^@ZB+$y5MGb#Vjlzp=N?ZGbX0r zzv`9X)y5{XQ1wUNq}1lwjOywS69v=c?;)0a6Ket=kG1gy`mQ%bI}A_+N^(7BD>?p< z`Q$%CTH>c=RfU@$R#1dgf8=kKqt4afPBc2!xG~KaxawI--LXcLWnK?bJ&@BbN{2qo z{9`b&(y#KWblUfSeo?j*yo2_Xt7o;Lv+ z`VZj%$)8XAUQ^=f|7dD^kD<-+=M=HRC+n2jr_**RvM!Edw=DP zb^#(9$V6)^$>~fO&7S3OTQER2Axp7h{Q$W^&*1ab0Ao*oTgxmAHl#+&PJEj zzfDsB4Z3)ld-~hSJ>Y7V6Ui*_7F*s-MoD@F+?wWZ*NWO0PD1D_w#BhOU^07Ae7`n~ zfM`k^q(sUoE#Dn~)G^E`S&pjsuN=iFo947$hYmF?!%%D{v8Z$T0y2d0mu>Go&Pnr# zWbdWtCJ0R9SPL*$yc*M6xCV1wR^rZD&((bzr zq9k?;>zrp0+W@g-BQwa?m1E~n=O59ZWrnS5h<~|>|1|jeFF5l$Vt$5R$j1+LAZiq} z6Bd=pP}1TP(0Q#BAxCF&@mcP;r8Y4^aZlgpE`MFK*X(0Wf<9thTz+)@`kSaRT)VQ7 zG{itHL@Qh)yirJxlUGbNxzrqV!St?q0G7<=>)B6EcpR=q!vxIn>4@x1fPRasnKfW? zE~`@ynW=MUqNR8@aMdxN+<^X)Xpu_k86FpLk1JOv=cDFyPDwoyI|NL7+=l38yg&U0 zW0>`Tf}1kPbAhW#kHW0$hGsTd>)-e{nLpZ()HUNFtQ-3-wg;vSFn+HU`xN`R-_1qh zry~%ouNeUCY(G_p>R9*TSx(9)a()90Ms^WkzxHxQN%cKA-93YXiRXks;Jf^U^WU(a z)%OzfN-#M!o>K=2>`y-+=J&yb(*%HNHz@r>>N7~~o@!__r#Uuq>VpoOhV`YVYE#}s zeMJqi@kC#7{<)k*e;6i3mGs$$k?|wYou|h3Mfa=e4hNX3lwM&=WsNS%s)?IyLQBl!&S8J=cyIb`H)MmBY8^Q}&0K^MfRJ^A| zr5A$u0_#*1=@lU;7L6aTs)fE8xC5$jk(?UFi&|8?iX_Oa8D>JkP8BaZ3AA}aZD8Zo zBxJ7L=l6EQ)M-~io44H)@rS3ApCJ6;g(ij&d(}y|8t(9#0)<1vJ_#4B^WxPoc-_fH zs>7LlbosidR#5b?Kq#QDP;gzy)5+ZW;np7vc6AHKfk@|CLDo{>hycM!vQ~uhrFG^^<18aYQTFd zUghUiTRS`< z0Me_j#jAPNg@Tbln`-WkWVY~@h)-5Wic_0&@fm7&NAOnl)(%hjif*;T(}nl`@Q%QA z5z<<`i-&~=SHwae=%CpUP*`0XS;xB7d{jGxfm7k%*o`cOiu^vjImf$z zzN=ao?AjRet?NK{UpQU8s<^nM5Fcggb-qru!Gp+=2^E2Q{6!?L;3ZArkZ)}y?4=e0 zmo)^k*onHgGX$?NRBP(oOkAm^cNNW57X~9}(m;?5u{RHSRlHUx7~06K;-E#yd8l8| zD}rSIra5Q4Qe0FrOW8o3Ou-Z+n}j$8fr#HfU8$;VsB_~Ny?9krVaXhKwJ20pD|`eh za`Y&pom?rz?19h~%t82Wlx7J045Cn2SC%Vm&?p*ISVJqk(UfIOhFU-oomyF<6!LDU z6w1D*G*h@mOa0#UUO$h$D991csq%%yJF8g5itrjH`3@_lxrhhHnIclIS>NeKlP-}~ zQ#{tudz^$rHqnVl&~)q-UeX)K1eK?mn|ITcB@D3XOJNj<_{O8du(<}!k7+?p_h?0~ zI{F?i?!}l;uz*l6HSoErf){($)z>a|)vu&tL-oR?OR5Ay3lV?)7jeXkzgSZEBqD+n z2zTAcCJ4b1jlrEx>qdOMXnK#2gal=1;Ld%{Q9o|C6e!nj<^SBbuBlGR-H# zQ%;rlobE^no+@{`U9l8Wk7Ab?|GeF3o5ALa^?0Qjy_s&jVwsdV??Fi7D=VdEU}S`T z{2V4cH;uL-pU>D#HzzTe! z+d+r&_*FqR^os!J_O<6U*y!+z z`2DyyrQsKVcNF)7;Nuqzin7R-yEVgQ%fCCb#-@75+wxtu+)A>iD360Z0rl>O>>)bd zgzSA8mA3qQGplWCBCFa~u+>^+E57@zDqC4}T$OFRC1cBYTNy|$TS28wg=i(SR((#! zkZp(MI@?a%yDT-feoK|@DNCj8u%#AS*MnCJ-ZJniO}u)t_zIXcV7kF{o0v<$>;P{G zcv^}g@lZ#E{0G3p_?~Y!<(0)>P4OeRL{{ag@n37(nUS&cjPX~VlTl~em9f;;pWy|p zgkkb)7DYM?FywFcB0+XJ|_uQ0*hT+G!F59D%d68H$O?>bRI?5MI0#dz4LTNMVI7%d~zg6 z_;zvjC7)$={EVNjvp7(6!57DjO1Z$m>@sthU@Z{`$HEoCR3o#~<@ z47$c~pwNHKqN@VGBcn^wv++wix@6pCB5@*2!~bM1X2{_`v#|O>oT=g$i45N%5;-!; zXPWpQhm1c}@bSN}7%r+StpDc$`FF@tEf%YsQXycSfNKQo60k?WZ31o=aHoL%0v;Cd zsDOh4Dwitk$`w!*uvowf0qX=@BVd<+JpyhMaJzsz1?(5_uz*Je928Lg@s`}FJPuXB zVgV}ztP^mJfL#Li2)Iqa?E>x;uwTH#0v;7`P(bA}QGWqd0gDB!5Rgm#{hv5}W|u{% z&~KFKl6cuwWyPhXSGUfY?VVFx;wdSfHK*89R_?vp)86hWZJS+MR$lC#Q`F|gmye|P z`A?rct`v25;9JA6XRT6%uco}AE~N;^``)5;fk+X)FXw+|W|Xux;=|ut^R)>nS7j}} zTwA9w@eU9EOiNMQ#z6PRP7cE%&XHf+F)@L(fEV)mJ)|H?UH&jdfxo6wgksUUpeW$m zY^8|4@==QLCrEsd4zj-=QsnJuX~!4(-jxTpfRX zgvE!yxr3D8o9CM;E&uRE^B1*AhOm^M$qN$7`BJcr_LKNT6lji;`ej02LRAV1DCaLp zz8nR!ezVY*&>;l{6w{_b#V>hJ(cdKWB~%SbUP#ri0U7^AGbrPi>wtus(3k5#YW&N< zryNRsxlTwZ7Z9o=>^=|8pp_Xzz;VNkAn63TUw^eJbl z`uBrIIg$1i6$o7_`#j}uV-O{^{pmX{v%ad56x3A)6!x7OFS1WM_ohq2Z=!70ue*$~ z6$zyrr;h4L`t}t4H9}uPRrKd{`aeq1@41{yN@$%bH93@iCH3bhnB$lGDhcI&OUkF_ z@7F?Kw!cy*Bn8|q8%&T>P-Rbwe!9QkDb!DEzu%|mSI~gMC1HO`z^VRUge-McnP$0< zlz-ndRbJu}z7AcxNnh?W^Zg*=l5!jy#U=d&7}R&Af4Lu$f3H>kT~-=@bV)tgj^752 z+E41seP%HYY+Q$lX}YAogr9(C)>nFja2JOK0+m4ON&IsZh$j74^l(mr&_8T+3{F=$ lFg`7Co)RMCNqzP`vmt%@_&p_dliVudpT5sGUn!{azX20D9LoRz diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart b/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart deleted file mode 100644 index 49c26a630..000000000 --- a/quill_native_bridge/quill_native_bridge_linux/lib/quill_native_bridge_linux.dart +++ /dev/null @@ -1,251 +0,0 @@ -// This file is referenced by pubspec.yaml. If you plan on moving this file -// Make sure to update pubspec.yaml to the new location. - -import 'dart:convert' show utf8; -import 'dart:io' show Process, File hide exitCode; - -import 'package:flutter/services.dart' show Uint8List; -import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; - -import 'src/binary_runner.dart'; -import 'src/constants.dart'; -import 'src/mime_types_constants.dart'; -import 'src/temp_file_utils.dart'; - -/// A Linux implementation of the [QuillNativeBridgePlatform]. -/// -/// **Highly Experimental** and can be removed. -/// -/// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: -/// -/// ```console -/// Assertion failed: "Platform interfaces must not be implemented with `implements`" -/// ``` -/// -/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) -/// and [QuillNativeBridgePlatform] for more details. -/// ``` -class QuillNativeBridgeLinux extends QuillNativeBridgePlatform { - QuillNativeBridgeLinux._(); - - static void registerWith() { - QuillNativeBridgePlatform.instance = QuillNativeBridgeLinux._(); - } - - @override - Future isSupported(QuillNativeBridgeFeature feature) async => { - QuillNativeBridgeFeature.getClipboardHtml, - QuillNativeBridgeFeature.copyHtmlToClipboard, - QuillNativeBridgeFeature.copyImageToClipboard, - QuillNativeBridgeFeature.getClipboardImage, - QuillNativeBridgeFeature.getClipboardFiles, - }.contains(feature); - - // TODO: Improve error handling - - // TODO: The xclipFile should always be removed in finally block, extractBinaryFromAsset() - // should be part of the try-catch - - // TODO: Support wayland https://github.com/bugaevc/wl-clipboard. - // Need to abstract implementation of xclip first. - - // TODO: Might want to improve the description of _hasClipboardItemOfType() - - /// Check if the system clipboard has [mimeType] to paste using [xclip](https://github.com/astrand/xclip). - /// - /// `xclip` doesn't throw an error when retrieving a clipboard item - /// while specifying type using `-t text/html`. - /// - /// Without this check, will return the last copied - /// item even if the last item is an image (as bytes). - /// - /// This only check the type in the clipboard selection. - Future _hasClipboardItemOfType({ - required String mimeType, - required String xclipFilePath, - }) async { - return (await Process.run( - xclipFilePath, ['-selection', 'clipboard', '-t', 'TARGETS', '-o'])) - .stdout - .toString() - .contains(mimeType); - } - - @override - Future getClipboardHtml() async { - final xclipFile = await extractBinaryFromAsset(kXclipAssetFile); - try { - // TODO: Write a test case where copying an image and then retrieving HTML - // should not throw an exception or unexpected behavior. Not required - // since some of the tests will fail if this issue happen. - - // TODO: Should check if the expected type is avalaible before - // avaliable before getting it using: xclip -o -t TARGETS - final hasHtmlInClipboard = await _hasClipboardItemOfType( - mimeType: kHtmlMimeType, - xclipFilePath: xclipFile.path, - ); - if (!hasHtmlInClipboard) { - return null; - } - final result = await Process.run( - xclipFile.path, - ['-selection', 'clipboard', '-o', '-t', kHtmlMimeType], - ); - if (result.exitCode == 0) { - return (result.stdout as String?)?.trim(); - } - final processErrorOutput = result.stderr.toString().trim(); - if (processErrorOutput - .startsWith('Error: target $kHtmlMimeType not available')) { - return null; - } - assert( - false, - 'Error retrieving the HTML to clipboard. Exit code: ${result.exitCode}\nError output: $processErrorOutput', - ); - } finally { - await xclipFile.delete(); - } - return null; - } - - @override - Future copyHtmlToClipboard(String html) async { - final xclipFile = await extractBinaryFromAsset(kXclipAssetFile); - - try { - final process = await Process.start( - xclipFile.path, - [ - '-selection', - 'clipboard', - '-t', - kHtmlMimeType, - ], - ); - process.stdin.writeln(html); - await process.stdin.close(); - final exitCode = await process.exitCode; - if (exitCode != 0) { - final processErrorOutput = - await process.stderr.transform(utf8.decoder).join(); - assert( - false, - 'Error copying the HTML to clipboard. Exit code: $exitCode\nError output: $processErrorOutput', - ); - } - } finally { - await xclipFile.delete(); - } - } - - @override - Future copyImageToClipboard(Uint8List imageBytes) async { - final xclipFile = await extractBinaryFromAsset(kXclipAssetFile); - final tempClipboardImageFileName = - 'tempClipboardImage-${DateTime.now().millisecondsSinceEpoch}.png'; - final tempClipboardImage = - File(generateTempFilePath(tempClipboardImageFileName)); - - try { - await tempClipboardImage.writeAsBytes(imageBytes); - - final process = await Process.start( - xclipFile.path, - [ - '-selection', - 'clipboard', - '-t', - kImagePngMimeType, - '-i', - tempClipboardImage.path, - ], - ); - final exitCode = await process.exitCode; - if (exitCode != 0) { - final errorOutput = await process.stderr.transform(utf8.decoder).join(); - assert( - false, - 'Error copying the image to clipboard. Exit code: $exitCode\nError output: $errorOutput', - ); - } - } finally { - await xclipFile.delete(); - await tempClipboardImage.delete(); - } - } - - @override - Future getClipboardImage() async { - final xclipFile = await extractBinaryFromAsset(kXclipAssetFile); - try { - final hasImagePngInClipboard = await _hasClipboardItemOfType( - mimeType: kImagePngMimeType, - xclipFilePath: xclipFile.path, - ); - if (!hasImagePngInClipboard) { - return null; - } - final result = await Process.run( - xclipFile.path, - ['-selection', 'clipboard', '-t', kImagePngMimeType, '-o'], - // Set stdoutEncoding to null. Expecting raw bytes. - stdoutEncoding: null, - ); - if (result.exitCode == 0) { - return result.stdout as Uint8List?; - } - final processErrorOutput = result.stderr.toString().trim(); - if (processErrorOutput - .startsWith('Error: target $kImagePngMimeType not available')) { - return null; - } - assert( - false, - 'Unknown error while retrieving image from the clipboard. Exit code: ${result.exitCode}. Error output $processErrorOutput', - ); - return null; - } finally { - await xclipFile.delete(); - } - } - - @override - Future> getClipboardFiles() async { - final xclipFile = await extractBinaryFromAsset(kXclipAssetFile); - try { - final hasFilesInClipboard = await _hasClipboardItemOfType( - mimeType: kUriListMimeType, - xclipFilePath: xclipFile.path, - ); - if (!hasFilesInClipboard) { - return []; - } - final result = await Process.run( - xclipFile.path, - ['-selection', 'clipboard', '-t', kUriListMimeType, '-o'], - ); - if (result.exitCode == 0) { - final output = result.stdout as String?; - if (output == null) return []; - return output.trim().split('\n').map((fileUriPath) { - // Necessary to remove percent-encoded characters and `file://` - return Uri.parse(fileUriPath).toFilePath().trim(); - }).toList(); - } - final processErrorOutput = result.stderr.toString().trim(); - if (processErrorOutput - .startsWith('Error: target $kUriListMimeType not available')) { - return []; - } - assert( - false, - 'Unknown error while retrieving image from the clipboard. Exit code: ${result.exitCode}. Error output $processErrorOutput', - ); - return []; - } finally { - await xclipFile.delete(); - } - } -} diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/src/binary_runner.dart b/quill_native_bridge/quill_native_bridge_linux/lib/src/binary_runner.dart deleted file mode 100644 index f48e1d401..000000000 --- a/quill_native_bridge/quill_native_bridge_linux/lib/src/binary_runner.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/services.dart'; - -import 'temp_file_utils.dart'; - -/// Extracts a binary file from the assets to a temporary location -/// to be executed. -/// -/// Copy the binary file located in the assets directory into a temporary -/// directory, and sets the file to be executable. -/// -/// - [assetFilePath] The name of the binary file to extract from assets. -/// -/// Returns: The extracted binary file as [File]. Should be removed when no longer needed. -/// Throws: [FileSystemException] can be thrown with `Text file busy` -/// if [_copyAssetTo] called while the file is executing. -Future extractBinaryFromAsset(String assetFilePath) async { - final extractedBinaryPath = generateTempFilePath(_getFileName(assetFilePath)); - final extractedBinaryFile = File(extractedBinaryPath); - - await _copyAssetTo( - assetFilePath: assetFilePath, - destinationFile: extractedBinaryFile, - ); - await _makeFileExecutable(extractedBinaryPath); - - return extractedBinaryFile; -} - -/// Copies the asset file to a destination directory. -/// -/// - [assetFilePath] The path of the asset file to be copied. -/// - [destinationFile] The target path where the asset file will be copied. -Future _copyAssetTo({ - required String assetFilePath, - required File destinationFile, -}) async { - final assetBytes = - (await rootBundle.load(assetFilePath)).buffer.asUint8List(); - - final parentDirectory = destinationFile.parent; - if (!(await parentDirectory.exists())) { - await parentDirectory.create(recursive: true); - } - - await destinationFile.writeAsBytes(assetBytes); -} - -/// Makes the specified file executable. -/// -/// - [filePath] The path of the file to make executable. -Future _makeFileExecutable(String filePath) async { - assert( - Platform.isLinux, - 'Must be on Linux to add execute permissions with chmod +x.', - ); - await Process.run('chmod', ['+x', filePath]); -} - -/// Extracts the file name from the file path. -/// -/// - [filePath] The full path of the file. -/// -/// Returns: The name of the file extracted from the path. -String _getFileName(String filePath) => filePath.split('/').last; diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/src/constants.dart b/quill_native_bridge/quill_native_bridge_linux/lib/src/constants.dart deleted file mode 100644 index c02998f84..000000000 --- a/quill_native_bridge/quill_native_bridge_linux/lib/src/constants.dart +++ /dev/null @@ -1,5 +0,0 @@ -/// The same package name in `pubspec.yaml` -const kPackageName = 'quill_native_bridge_linux'; - -/// The asset file path of [xclip](https://github.com/astrand/xclip) binary. -const kXclipAssetFile = 'packages/$kPackageName/assets/xclip'; diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/src/mime_types_constants.dart b/quill_native_bridge/quill_native_bridge_linux/lib/src/mime_types_constants.dart deleted file mode 100644 index 2d68f0f35..000000000 --- a/quill_native_bridge/quill_native_bridge_linux/lib/src/mime_types_constants.dart +++ /dev/null @@ -1,6 +0,0 @@ -// TODO: This is used in web and linux implementation, might extract it in -// common interface - -const String kHtmlMimeType = 'text/html'; -const String kImagePngMimeType = 'image/png'; -const String kUriListMimeType = 'text/uri-list'; diff --git a/quill_native_bridge/quill_native_bridge_linux/lib/src/temp_file_utils.dart b/quill_native_bridge/quill_native_bridge_linux/lib/src/temp_file_utils.dart deleted file mode 100644 index 0bebdc964..000000000 --- a/quill_native_bridge/quill_native_bridge_linux/lib/src/temp_file_utils.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'dart:io' show Directory; - -import 'constants.dart'; - -/// Create a path in the system's temporary directory for a given file name. -/// -/// - [fileName] The name of the file to be stored. -/// -/// Returns: The path where the file will be located. -String generateTempFilePath(String fileName) => - '${Directory.systemTemp.path}/$kPackageName/$fileName'; diff --git a/quill_native_bridge/quill_native_bridge_linux/pubspec.yaml b/quill_native_bridge/quill_native_bridge_linux/pubspec.yaml deleted file mode 100644 index 3ec65aa4d..000000000 --- a/quill_native_bridge/quill_native_bridge_linux/pubspec.yaml +++ /dev/null @@ -1,34 +0,0 @@ -name: quill_native_bridge_linux -description: "Linux implementation of the quill_native_bridge plugin." -version: 0.0.1-dev.1 -homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_linux -repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_linux -issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ -documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_linux - -environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' - -dependencies: - flutter: - sdk: flutter - quill_native_bridge_platform_interface: ^0.0.1-dev.2 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^4.0.0 - crypto: ^3.0.5 - yaml: ^3.1.2 - -flutter: - assets: - - assets/xclip - plugin: - implements: quill_native_bridge - platforms: - linux: - pluginClass: none - dartPluginClass: QuillNativeBridgeLinux - fileName: quill_native_bridge_linux.dart \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_linux/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_linux/pubspec_overrides.yaml deleted file mode 100644 index a07f95b27..000000000 --- a/quill_native_bridge/quill_native_bridge_linux/pubspec_overrides.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing -dependency_overrides: - quill_native_bridge_platform_interface: - path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_linux/test/validate_package_name_constant.dart b/quill_native_bridge/quill_native_bridge_linux/test/validate_package_name_constant.dart deleted file mode 100644 index dfeee99f9..000000000 --- a/quill_native_bridge/quill_native_bridge_linux/test/validate_package_name_constant.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:io'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:quill_native_bridge_linux/src/constants.dart'; -import 'package:yaml/yaml.dart'; - -void main() { - test('the package name constant should match the one in pubspec.yaml', () { - const pubspecYamlFileName = 'pubspec.yaml'; - final pubspecYamlFile = File(pubspecYamlFileName); - if (!pubspecYamlFile.existsSync()) { - fail( - "The '$pubspecYamlFileName' file doesn't exist. Run the test from the package root directory.", - ); - } - final pubspecYaml = loadYaml(pubspecYamlFile.readAsStringSync()) as YamlMap; - final pubspecYamlPackageName = pubspecYaml['name'] as String?; - - expect(kPackageName, pubspecYamlPackageName); - }); -} diff --git a/quill_native_bridge/quill_native_bridge_linux/test/validate_xclip_test.dart b/quill_native_bridge/quill_native_bridge_linux/test/validate_xclip_test.dart deleted file mode 100644 index 5627ba1d8..000000000 --- a/quill_native_bridge/quill_native_bridge_linux/test/validate_xclip_test.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:crypto/crypto.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:quill_native_bridge_linux/src/constants.dart'; - -void main() async { - testWidgets('validate xclip binary file', (tester) async { - // Latest Xclip binary file https://github.com/astrand/xclip - const latestXclipSha512Checksum = - 'f18ad061b8711c7b955edec8e5b203566e9c705da466733d148968a66fbce6db89e540790cc834e2390e4d42d8973b7c0245224e54aadd0133b201b37d3bed79'; - - // Ensure the xclip file is in the assets directory and accessible - // without any runtime issues. - final xclipAssetFileBytes = - (await rootBundle.load(kXclipAssetFile)).buffer.asUint8List(); - final xclipAssetFileSha512Checksum = - sha512.convert(xclipAssetFileBytes).toString(); - - expect(xclipAssetFileSha512Checksum, latestXclipSha512Checksum); - }); -} diff --git a/quill_native_bridge/quill_native_bridge_macos/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_macos/CHANGELOG.md deleted file mode 100644 index 13b5ca517..000000000 --- a/quill_native_bridge/quill_native_bridge_macos/CHANGELOG.md +++ /dev/null @@ -1,11 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -## 0.0.1-dev.1 - -- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. - -## 0.0.1-dev.0 - -- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_macos/LICENSE b/quill_native_bridge/quill_native_bridge_macos/LICENSE deleted file mode 100644 index e7ff73e1b..000000000 --- a/quill_native_bridge/quill_native_bridge_macos/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_macos/README.md b/quill_native_bridge/quill_native_bridge_macos/README.md deleted file mode 100644 index 7f17d71af..000000000 --- a/quill_native_bridge/quill_native_bridge_macos/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# đŸĒļ Quill Native Bridge - -The macOS implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). - -## ⚙ī¸ Usage - -This package is endorsed, which means you can simply use `quill_native_bridge` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. - -However, if you import this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. - -## 📉 Note on breaking changes - -The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart b/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart deleted file mode 100644 index 04c816516..000000000 --- a/quill_native_bridge/quill_native_bridge_macos/lib/quill_native_bridge_macos.dart +++ /dev/null @@ -1,70 +0,0 @@ -// This file is referenced by pubspec.yaml. If you plan on moving this file -// Make sure to update pubspec.yaml to the new location. - -import 'package:flutter/foundation.dart'; -import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; - -import 'src/messages.g.dart'; - -/// An implementation of [QuillNativeBridgePlatform] for macOS. -/// -/// **Highly Experimental** and can be removed. -/// -/// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: -/// -/// ```console -/// Assertion failed: "Platform interfaces must not be implemented with `implements`" -/// ``` -/// -/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) -/// and [QuillNativeBridgePlatform] for more details. -class QuillNativeBridgeMacOS extends QuillNativeBridgePlatform { - QuillNativeBridgeMacOS._({ - @visibleForTesting QuillNativeBridgeApi? api, - }) : _hostApi = api ?? QuillNativeBridgeApi(); - - final QuillNativeBridgeApi _hostApi; - - /// Registers this class as the default instance of [QuillNativeBridgePlatform]. - static void registerWith() { - assert( - defaultTargetPlatform == TargetPlatform.macOS && !kIsWeb, - '$QuillNativeBridgeMacOS should be only used for macOS.', - ); - QuillNativeBridgePlatform.instance = QuillNativeBridgeMacOS._(); - } - - @override - Future isSupported(QuillNativeBridgeFeature feature) async => { - QuillNativeBridgeFeature.getClipboardHtml, - QuillNativeBridgeFeature.copyHtmlToClipboard, - QuillNativeBridgeFeature.copyImageToClipboard, - QuillNativeBridgeFeature.getClipboardImage, - QuillNativeBridgeFeature.getClipboardFiles, - }.contains(feature); - - @override - Future isIOSSimulator() => throw UnsupportedError( - 'isIOSSimulator() is only supported on iOS.', - ); - - @override - Future getClipboardHtml() => _hostApi.getClipboardHtml(); - - @override - Future copyHtmlToClipboard(String html) => - _hostApi.copyHtmlToClipboard(html); - - @override - Future getClipboardImage() => _hostApi.getClipboardImage(); - - @override - Future copyImageToClipboard(Uint8List imageBytes) => - _hostApi.copyImageToClipboard(imageBytes); - - @override - Future getClipboardGif() => _hostApi.getClipboardGif(); - - @override - Future> getClipboardFiles() => _hostApi.getClipboardFiles(); -} diff --git a/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart b/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart deleted file mode 100644 index 350dec008..000000000 --- a/quill_native_bridge/quill_native_bridge_macos/lib/src/messages.g.dart +++ /dev/null @@ -1,185 +0,0 @@ -// Autogenerated from Pigeon (v22.4.0), do not edit directly. -// See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers - -import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; - -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; -import 'package:flutter/services.dart'; - -PlatformException _createConnectionError(String channelName) { - return PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel: "$channelName".', - ); -} - -class _PigeonCodec extends StandardMessageCodec { - const _PigeonCodec(); -} - -class QuillNativeBridgeApi { - /// Constructor for [QuillNativeBridgeApi]. The [binaryMessenger] named argument is - /// available for dependency injection. If it is left null, the default - /// BinaryMessenger will be used which routes to the host platform. - QuillNativeBridgeApi( - {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) - : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; - final BinaryMessenger? pigeonVar_binaryMessenger; - - static const MessageCodec pigeonChannelCodec = _PigeonCodec(); - - final String pigeonVar_messageChannelSuffix; - - Future getClipboardHtml() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardHtml$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as String?); - } - } - - Future copyHtmlToClipboard(String html) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyHtmlToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send([html]) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - - Future getClipboardImage() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardImage$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as Uint8List?); - } - } - - Future copyImageToClipboard(Uint8List imageBytes) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyImageToClipboard$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send([imageBytes]) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - - Future getClipboardGif() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardGif$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as Uint8List?); - } - } - - Future> getClipboardFiles() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardFiles$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as List?)!.cast(); - } - } -} diff --git a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/Messages.g.swift b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/Messages.g.swift deleted file mode 100644 index 1bf9c3b96..000000000 --- a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/Messages.g.swift +++ /dev/null @@ -1,186 +0,0 @@ -// Autogenerated from Pigeon (v22.4.0), do not edit directly. -// See also: https://pub.dev/packages/pigeon - -import Foundation - -#if os(iOS) - import Flutter -#elseif os(macOS) - import FlutterMacOS -#else - #error("Unsupported platform.") -#endif - -/// Error class for passing custom error details to Dart side. -final class PigeonError: Error { - let code: String - let message: String? - let details: Any? - - init(code: String, message: String?, details: Any?) { - self.code = code - self.message = message - self.details = details - } - - var localizedDescription: String { - return - "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" - } -} - -private func wrapResult(_ result: Any?) -> [Any?] { - return [result] -} - -private func wrapError(_ error: Any) -> [Any?] { - if let pigeonError = error as? PigeonError { - return [ - pigeonError.code, - pigeonError.message, - pigeonError.details, - ] - } - if let flutterError = error as? FlutterError { - return [ - flutterError.code, - flutterError.message, - flutterError.details, - ] - } - return [ - "\(error)", - "\(type(of: error))", - "Stacktrace: \(Thread.callStackSymbols)", - ] -} - -private func isNullish(_ value: Any?) -> Bool { - return value is NSNull || value == nil -} - -private func nilOrValue(_ value: Any?) -> T? { - if value is NSNull { return nil } - return value as! T? -} - -private class MessagesPigeonCodecReader: FlutterStandardReader { -} - -private class MessagesPigeonCodecWriter: FlutterStandardWriter { -} - -private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { - override func reader(with data: Data) -> FlutterStandardReader { - return MessagesPigeonCodecReader(data: data) - } - - override func writer(with data: NSMutableData) -> FlutterStandardWriter { - return MessagesPigeonCodecWriter(data: data) - } -} - -class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { - static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter()) -} - -/// Generated protocol from Pigeon that represents a handler of messages from Flutter. -protocol QuillNativeBridgeApi { - func getClipboardHtml() throws -> String? - func copyHtmlToClipboard(html: String) throws - func getClipboardImage() throws -> FlutterStandardTypedData? - func copyImageToClipboard(imageBytes: FlutterStandardTypedData) throws - func getClipboardGif() throws -> FlutterStandardTypedData? - func getClipboardFiles() throws -> [String] -} - -/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. -class QuillNativeBridgeApiSetup { - static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared } - /// Sets up an instance of `QuillNativeBridgeApi` to handle messages through the `binaryMessenger`. - static func setUp(binaryMessenger: FlutterBinaryMessenger, api: QuillNativeBridgeApi?, messageChannelSuffix: String = "") { - let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" - let getClipboardHtmlChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardHtml\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - getClipboardHtmlChannel.setMessageHandler { _, reply in - do { - let result = try api.getClipboardHtml() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) - } - } - } else { - getClipboardHtmlChannel.setMessageHandler(nil) - } - let copyHtmlToClipboardChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyHtmlToClipboard\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - copyHtmlToClipboardChannel.setMessageHandler { message, reply in - let args = message as! [Any?] - let htmlArg = args[0] as! String - do { - try api.copyHtmlToClipboard(html: htmlArg) - reply(wrapResult(nil)) - } catch { - reply(wrapError(error)) - } - } - } else { - copyHtmlToClipboardChannel.setMessageHandler(nil) - } - let getClipboardImageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardImage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - getClipboardImageChannel.setMessageHandler { _, reply in - do { - let result = try api.getClipboardImage() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) - } - } - } else { - getClipboardImageChannel.setMessageHandler(nil) - } - let copyImageToClipboardChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.copyImageToClipboard\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - copyImageToClipboardChannel.setMessageHandler { message, reply in - let args = message as! [Any?] - let imageBytesArg = args[0] as! FlutterStandardTypedData - do { - try api.copyImageToClipboard(imageBytes: imageBytesArg) - reply(wrapResult(nil)) - } catch { - reply(wrapError(error)) - } - } - } else { - copyImageToClipboardChannel.setMessageHandler(nil) - } - let getClipboardGifChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardGif\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - getClipboardGifChannel.setMessageHandler { _, reply in - do { - let result = try api.getClipboardGif() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) - } - } - } else { - getClipboardGifChannel.setMessageHandler(nil) - } - let getClipboardFilesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quill_native_bridge_macos.QuillNativeBridgeApi.getClipboardFiles\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - getClipboardFilesChannel.setMessageHandler { _, reply in - do { - let result = try api.getClipboardFiles() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) - } - } - } else { - getClipboardFilesChannel.setMessageHandler(nil) - } - } -} diff --git a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgeImpl.swift b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgeImpl.swift deleted file mode 100644 index 7593168a3..000000000 --- a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgeImpl.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Foundation -import FlutterMacOS - -class QuillNativeBridgeImpl: QuillNativeBridgeApi { - func getClipboardHtml() throws -> String? { - guard let htmlData = NSPasteboard.general.data(forType: .html) else { - return nil - } - let html = String(data: htmlData, encoding: .utf8) - return html - } - - func copyHtmlToClipboard(html: String) throws { - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setString(html, forType: .html) - } - - func getClipboardImage() throws -> FlutterStandardTypedData? { - // TODO: This can return null when copying an image from some apps (e.g Telegram, Apple notes), seems to work with macOS screenshot and Google Chrome, attemp to fix it later - guard let image = NSPasteboard.general.readObjects(forClasses: [NSImage.self], options: nil)?.first as? NSImage else { - return nil - } - guard let tiffData = image.tiffRepresentation, - let bitmap = NSBitmapImageRep(data: tiffData), - let pngData = bitmap.representation(using: .png, properties: [:]) else { - return nil - } - return FlutterStandardTypedData(bytes: pngData) - } - - func copyImageToClipboard(imageBytes: FlutterStandardTypedData) throws { - guard let image = NSImage(data: imageBytes.data) else { - throw PigeonError(code: "INVALID_IMAGE", message: "Unable to create NSImage from image bytes.", details: nil) - } - - guard let tiffData = image.tiffRepresentation else { - throw PigeonError(code: "INVALID_IMAGE", message: "Unable to get TIFF representation from NSImage.", details: nil) - } - - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setData(tiffData, forType: .png) - } - - func getClipboardGif() throws -> FlutterStandardTypedData? { - let availableTypes = NSPasteboard.general.types - throw PigeonError(code: "GIF_UNSUPPORTED", message: "Gif image is not supported on macOS. Available types: \(String(describing: availableTypes))", details: nil) - } - - func getClipboardFiles() throws -> [String] { - guard let urlList = NSPasteboard.general.readObjects(forClasses: [NSURL.self], options: nil) as? [NSURL] else { - return [] - } - return urlList.compactMap { url in url.path } - } -} diff --git a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgePlugin.swift deleted file mode 100644 index 7aeb6a320..000000000 --- a/quill_native_bridge/quill_native_bridge_macos/macos/Classes/QuillNativeBridgePlugin.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Cocoa -import FlutterMacOS - -public class QuillNativeBridgePlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let messenger = registrar.messenger - let api = QuillNativeBridgeImpl() - QuillNativeBridgeApiSetup.setUp(binaryMessenger: messenger, api: api) - } -} diff --git a/quill_native_bridge/quill_native_bridge_macos/macos/quill_native_bridge_macos.podspec b/quill_native_bridge/quill_native_bridge_macos/macos/quill_native_bridge_macos.podspec deleted file mode 100644 index 4df29742a..000000000 --- a/quill_native_bridge/quill_native_bridge_macos/macos/quill_native_bridge_macos.podspec +++ /dev/null @@ -1,23 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint quill_native_bridge_macos.podspec` to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'quill_native_bridge_macos' - s.version = '0.0.1' - s.summary = 'A plugin for flutter_quill' - s.description = <<-DESC -An internal plugin for flutter_quill package to access platform-specific APIs. - DESC - s.homepage = 'https://github.com/singerdmx/flutter-quill' - s.license = { :file => '../LICENSE' } - s.author = { 'Flutter Quill' => 'https://github.com/singerdmx/flutter-quill' } - - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'FlutterMacOS' - - s.platform = :osx, '10.11' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - s.swift_version = '5.0' -end diff --git a/quill_native_bridge/quill_native_bridge_macos/pigeons/messages.dart b/quill_native_bridge/quill_native_bridge_macos/pigeons/messages.dart deleted file mode 100644 index 3048b62db..000000000 --- a/quill_native_bridge/quill_native_bridge_macos/pigeons/messages.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:pigeon/pigeon.dart'; - -@ConfigurePigeon(PigeonOptions( - dartOut: 'lib/src/messages.g.dart', - swiftOut: 'macos/Classes/Messages.g.swift', - dartPackageName: 'quill_native_bridge_macos', -)) -@HostApi() -abstract class QuillNativeBridgeApi { - // HTML - String? getClipboardHtml(); - void copyHtmlToClipboard(String html); - - // Image - Uint8List? getClipboardImage(); - void copyImageToClipboard(Uint8List imageBytes); - Uint8List? getClipboardGif(); - - // File - List getClipboardFiles(); -} diff --git a/quill_native_bridge/quill_native_bridge_macos/pubspec.yaml b/quill_native_bridge/quill_native_bridge_macos/pubspec.yaml deleted file mode 100644 index 00c8a7279..000000000 --- a/quill_native_bridge/quill_native_bridge_macos/pubspec.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: quill_native_bridge_macos -description: "macOS implementation of the quill_native_bridge plugin." -version: 0.0.1-dev.1 -homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_macos -repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_macos -issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ -documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_macos - -environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' - -dependencies: - flutter: - sdk: flutter - quill_native_bridge_platform_interface: ^0.0.1-dev.2 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^4.0.0 - pigeon: ^22.4.0 - -flutter: - plugin: - implements: quill_native_bridge - platforms: - macos: - pluginClass: QuillNativeBridgePlugin - dartPluginClass: QuillNativeBridgeMacOS diff --git a/quill_native_bridge/quill_native_bridge_macos/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_macos/pubspec_overrides.yaml deleted file mode 100644 index a07f95b27..000000000 --- a/quill_native_bridge/quill_native_bridge_macos/pubspec_overrides.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing -dependency_overrides: - quill_native_bridge_platform_interface: - path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_platform_interface/CHANGELOG.md deleted file mode 100644 index 9ccec2f2d..000000000 --- a/quill_native_bridge/quill_native_bridge_platform_interface/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -## 0.0.1-dev.2 - -- Identical to `0.0.1-dev.1` - -## 0.0.1-dev.1 - -- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. - -## 0.0.1-dev.0 - -- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/LICENSE b/quill_native_bridge/quill_native_bridge_platform_interface/LICENSE deleted file mode 100644 index e7ff73e1b..000000000 --- a/quill_native_bridge/quill_native_bridge_platform_interface/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_platform_interface/README.md b/quill_native_bridge/quill_native_bridge_platform_interface/README.md deleted file mode 100644 index 99150a14a..000000000 --- a/quill_native_bridge/quill_native_bridge_platform_interface/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# đŸĒļ Quill Native Bridge - -A common platform interface for the [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge) plugin. - -This interface allows platform-specific implementations of the `quill_native_bridge` plugin, as well as the plugin itself, to ensure they are supporting the same interface. - -## ⚙ī¸ Usage - -To implement a new platform-specific implementation of `quill_native_bridge`, extend [`QuillNativeBridgePlatform`](./lib/quill_native_bridge_platform_interface.dart) with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `QuillNativeBridgePlatform` by calling: - -```dart -QuillNativeBridgePlatform.instance = MyPlatformQuillNativeBridge(); -``` - -## 📉 Note on breaking changes - -The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart deleted file mode 100644 index bedf7481c..000000000 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/quill_native_bridge_platform_interface.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:flutter/foundation.dart' show Uint8List; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -import 'src/platform_feature.dart'; -import 'src/quill_native_bridge_method_channel.dart'; - -export 'src/platform_feature.dart'; - -/// **Experimental** as breaking changes can occur. -/// -/// Platform implementations should extend this class rather than implement it -/// as newly added methods are not considered to be breaking -/// changes. Extending this class (using `extends`) ensures that the subclass -/// will get the default implementation, while platform implementations that -/// `implements` this interface will be broken by newly added -/// [QuillNativeBridgePlatform] methods. -/// -/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) -/// and [plugin_platform_interface](https://pub.dev/packages/plugin_platform_interface) -/// for more details. -abstract class QuillNativeBridgePlatform extends PlatformInterface { - /// Constructs a QuillNativeBridgePlatform. - QuillNativeBridgePlatform() : super(token: _token); - - /// Avoid using `const` when creating the `Object` for `_token` - static final Object _token = Object(); - - static QuillNativeBridgePlatform _instance = MethodChannelQuillNativeBridge(); - - /// The default instance of [QuillNativeBridgePlatform] to use. - /// - /// Defaults to [MethodChannelQuillNativeBridge]. - static QuillNativeBridgePlatform get instance => _instance; - - /// Platform-specific implementations should set this with their own - /// platform-specific class that extends [QuillNativeBridgePlatform] when - /// they register themselves. - static set instance(QuillNativeBridgePlatform instance) { - PlatformInterface.verify(instance, _token); - _instance = instance; - } - - /// Checks if the specified [feature] is supported in the current implementation. - /// - /// Will verify if this is supported in the platform itself: - /// - /// - If [feature] is supported on **Android API 21** (as an example) and the - /// current Android API is `19` then will return `false` - /// - If [feature] is supported on the web if Clipboard API (as another example) - /// available in the current browser, and the current browser doesn't support it, - /// will return `false` too. For this specific example, you will need - /// to fallback to **Clipboard events** on **Firefox** or browsers that doesn't - /// support **Clipboard API**. - /// - /// Always check the docs of the method you're calling to see if there - /// are special notes. - Future isSupported(QuillNativeBridgeFeature feature) => - throw UnimplementedError('isSupported() has not been implemented.'); - - /// Check if the app is running on [iOS Simulator](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device). - Future isIOSSimulator() => - throw UnimplementedError('isIOSSimulator() has not been implemented.'); - - /// Return HTML from the Clipboard. - Future getClipboardHtml() => - throw UnimplementedError('getClipboardHtml() has not been implemented.'); - - /// Copy the [html] to the clipboard to be pasted on other apps. - Future copyHtmlToClipboard(String html) => throw UnimplementedError( - 'copyHtmlToClipboard() has not been implemented.'); - - /// Copy the [imageBytes] to Clipboard to be pasted on other apps. - Future copyImageToClipboard(Uint8List imageBytes) => - throw UnimplementedError( - 'copyImageToClipboard() has not been implemented.', - ); - - /// Return the copied image from the Clipboard. - Future getClipboardImage() => - throw UnimplementedError('getClipboardImage() has not been implemented.'); - - /// Return the copied gif from the Clipboard. - Future getClipboardGif() => - throw UnimplementedError('getClipboardGif() has not been implemented.'); - - /// Return the file paths from the Clipboard. - Future> getClipboardFiles() => - throw UnimplementedError('getClipboardFiles() has not been implemented.'); -} diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart deleted file mode 100644 index 952bf8d0a..000000000 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/platform_feature.dart +++ /dev/null @@ -1,82 +0,0 @@ -/// The platform features provided by the plugin -enum QuillNativeBridgeFeature { - isIOSSimulator, - getClipboardHtml, - copyHtmlToClipboard, - copyImageToClipboard, - getClipboardImage, - getClipboardGif, - getClipboardFiles; - - const QuillNativeBridgeFeature(); - - // TODO: Remove those comments later - - // /// Verify if this feature is supported on web regardless of the [TargetPlatform]. - // /// - // /// **Note**: This doesn't check whatever if the web browser support this - // /// specific feature. - // /// - // /// For example the **Clipboard API** might not be supported on **Firefox** - // /// but is supported on the web itself in general, the [hasWebSupport] - // /// will return `true`. - // /// - // /// Always check the docs of the method you're calling to see if there - // /// are special notes. For this specific example, you will need - // /// to fallback to **Clipboard events** on **Firefox** or browsers that doesn't - // /// support **Clipboard API**. - // final bool hasWebSupport; - - // /// Verify whether a specific feature is supported by the plugin for the [TargetPlatform]. - // /// - // /// **Note**: This doesn't check if the platform operating system does support - // /// this feature. It only check if this feature is supported - // /// on a specific platform (e.g. **Android** or **iOS**). - // /// - // /// If feature A is not supported on **Android API 21** (for example), - // /// then the [isSupported] doesn't cover this case. - // /// - // /// Always check the docs of the method you're calling to see if there - // /// are special notes. - // bool get isSupported { - // if (kIsWeb) { - // return hasWebSupport; - // } - // return switch (this) { - // QuillNativeBridgeFeature.isIOSSimulator => - // !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS, - // QuillNativeBridgeFeature.getClipboardHtml => { - // TargetPlatform.android, - // TargetPlatform.iOS, - // TargetPlatform.macOS, - // TargetPlatform.windows, - // TargetPlatform.linux, - // }.contains(defaultTargetPlatform), - // QuillNativeBridgeFeature.copyHtmlToClipboard => { - // TargetPlatform.android, - // TargetPlatform.iOS, - // TargetPlatform.macOS, - // TargetPlatform.linux, - // }.contains(defaultTargetPlatform), - // QuillNativeBridgeFeature.copyImageToClipboard => { - // TargetPlatform.android, - // TargetPlatform.iOS, - // TargetPlatform.macOS, - // TargetPlatform.linux, - // }.contains(defaultTargetPlatform), - // QuillNativeBridgeFeature.getClipboardImage => { - // TargetPlatform.android, - // TargetPlatform.iOS, - // TargetPlatform.macOS, - // TargetPlatform.linux, - // }.contains(defaultTargetPlatform), - // QuillNativeBridgeFeature.getClipboardGif => { - // TargetPlatform.android, - // TargetPlatform.iOS - // }.contains(defaultTargetPlatform), - // }; - // } - - // /// Negation of [isSupported] - // bool get isUnsupported => !isSupported; -} diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart b/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart deleted file mode 100644 index 011fcec1a..000000000 --- a/quill_native_bridge/quill_native_bridge_platform_interface/lib/src/quill_native_bridge_method_channel.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'dart:io' as io show Platform; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart' show MethodChannel; - -import '../quill_native_bridge_platform_interface.dart'; - -// TODO: This class is no longer used for implementations that use method channel. -// Instead each platform (e.g. Android) have their own implementation which might -// or might not use method channel, might remove this class completely - -const _methodChannel = MethodChannel('quill_native_bridge'); - -/// A default [QuillNativeBridgePlatform] implementation backed by a platform -/// channel. -class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { - /// For tests only - @visibleForTesting - MethodChannel get testMethodChannel { - assert(() { - if (kIsWeb) { - throw StateError( - 'Could not check if this was a test on web. Method channel should' - 'be only accessed for tests outside of $MethodChannelQuillNativeBridge', - ); - } - if (!io.Platform.environment.containsKey('FLUTTER_TEST')) { - throw StateError( - 'The method channel should be only accessed in tests when used ' - 'outside of $MethodChannelQuillNativeBridge', - ); - } - return true; - }()); - return _methodChannel; - } - - @override - Future isSupported(QuillNativeBridgeFeature feature) async { - final isSupported = await _methodChannel.invokeMethod( - 'isSupported', - feature.name, - ); - return isSupported ?? false; - } - - @override - Future isIOSSimulator() async { - assert(() { - if (defaultTargetPlatform != TargetPlatform.iOS || kIsWeb) { - throw FlutterError( - 'isIOSSimulator() method should be called only on iOS.', - ); - } - return true; - }()); - final isSimulator = - await _methodChannel.invokeMethod('isIOSSimulator'); - assert(() { - if (isSimulator == null) { - throw FlutterError( - 'isSimulator should not be null.', - ); - } - return true; - }()); - return isSimulator ?? false; - } - - @override - Future getClipboardHtml() async { - final htmlText = - await _methodChannel.invokeMethod('getClipboardHtml'); - return htmlText; - } - - @override - Future copyHtmlToClipboard(String html) async { - await _methodChannel.invokeMethod( - 'copyHtmlToClipboard', - html, - ); - } - - @override - Future copyImageToClipboard(Uint8List imageBytes) async { - await _methodChannel.invokeMethod( - 'copyImageToClipboard', - imageBytes, - ); - } - - // TODO: getClipboardImage() should not return gif files on macOS and iOS, same as Android impl - - @override - Future getClipboardImage() async { - final imageBytes = await _methodChannel.invokeMethod( - 'getClipboardImage', - ); - return imageBytes; - } - - @override - Future getClipboardGif() async { - final gifBytes = await _methodChannel.invokeMethod( - 'getClipboardGif', - ); - return gifBytes; - } - - @override - Future> getClipboardFiles() async { - final filePaths = await _methodChannel.invokeMethod?>( - 'getClipboardGif', - ); - return filePaths ?? []; - } -} diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/pubspec.yaml b/quill_native_bridge/quill_native_bridge_platform_interface/pubspec.yaml deleted file mode 100644 index 2eb38e9da..000000000 --- a/quill_native_bridge/quill_native_bridge_platform_interface/pubspec.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: quill_native_bridge_platform_interface -description: "A common platform interface for the quill_native_bridge plugin." -version: 0.0.1-dev.2 -homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_platform_interface -repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_platform_interface -issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ -documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_platform_interface - -environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' - -dependencies: - flutter: - sdk: flutter - plugin_platform_interface: ^2.1.8 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^5.0.0 - -flutter: - plugin: - platforms: - ios: - pluginClass: QuillNativeBridgePlugin diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_method_channel_test.dart b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_method_channel_test.dart deleted file mode 100644 index 341cedd11..000000000 --- a/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_method_channel_test.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:quill_native_bridge_platform_interface/src/quill_native_bridge_method_channel.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - final platform = MethodChannelQuillNativeBridge(); - const channel = MethodChannel('quill_native_bridge'); - - setUp(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( - channel, - (methodCall) async { - return false; - }, - ); - }); - - tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(channel, null); - }); - - test('isIOSSimulator', () async { - debugDefaultTargetPlatformOverride = TargetPlatform.iOS; - expect(await platform.isIOSSimulator(), false); - }); -} diff --git a/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart b/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart deleted file mode 100644 index 11d2e07a8..000000000 --- a/quill_native_bridge/quill_native_bridge_platform_interface/test/quill_native_bridge_test.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; -import 'package:quill_native_bridge_platform_interface/src/quill_native_bridge_method_channel.dart'; - -class MockQuillNativeBridgePlatform - with MockPlatformInterfaceMixin - implements QuillNativeBridgePlatform { - @override - Future isSupported(QuillNativeBridgeFeature feature) async { - return false; - } - - @override - Future isIOSSimulator() async => false; - - @override - Future getClipboardHtml() async { - return '

Invalid HTML
'; - } - - String? primaryHTMLClipbaord; - - @override - Future copyHtmlToClipboard(String html) async { - primaryHTMLClipbaord = html; - } - - Uint8List? primaryImageClipboard; - - @override - Future copyImageToClipboard(Uint8List imageBytes) async { - primaryImageClipboard = imageBytes; - } - - @override - Future getClipboardImage() async { - return Uint8List.fromList([0, 2, 1]); - } - - @override - Future getClipboardGif() async { - return Uint8List.fromList([0, 1, 0]); - } - - @override - Future> getClipboardFiles() async { - return ['/path/to/file.html', 'path/to/file.md']; - } -} - -void main() { - final initialPlatform = QuillNativeBridgePlatform.instance; - - test('$MethodChannelQuillNativeBridge is the default instance', () { - expect(initialPlatform, isInstanceOf()); - }); - - final fakePlatform = MockQuillNativeBridgePlatform(); - QuillNativeBridgePlatform.instance = fakePlatform; - - test('isIOSSimulator', () async { - debugDefaultTargetPlatformOverride = TargetPlatform.iOS; - expect(await QuillNativeBridgePlatform.instance.isIOSSimulator(), false); - }); - - test('getClipboardHtml()', () async { - expect( - await QuillNativeBridgePlatform.instance.getClipboardHtml(), - '
Invalid HTML
', - ); - }); - - test('copyImageToClipboard()', () async { - final imageBytes = Uint8List.fromList([]); - expect( - fakePlatform.primaryImageClipboard, - null, - ); - await QuillNativeBridgePlatform.instance.copyImageToClipboard(imageBytes); - expect( - fakePlatform.primaryImageClipboard, - imageBytes, - ); - }); - - test('copyHtmlToClipboard()', () async { - const html = '
HTML
'; - expect( - fakePlatform.primaryHTMLClipbaord, - null, - ); - await QuillNativeBridgePlatform.instance.copyHtmlToClipboard(html); - expect( - fakePlatform.primaryHTMLClipbaord, - html, - ); - }); - - test('getClipboardImage()', () async { - expect( - await QuillNativeBridgePlatform.instance.getClipboardImage(), - Uint8List.fromList([0, 2, 1]), - ); - }); - - test('getClipboardGif()', () async { - expect( - await QuillNativeBridgePlatform.instance.getClipboardGif(), - Uint8List.fromList([0, 1, 0]), - ); - }); - test('getClipboardFiles()', () async { - expect( - await QuillNativeBridgePlatform.instance.getClipboardFiles(), - ['/path/to/file.html', 'path/to/file.md'], - ); - }); -} diff --git a/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md deleted file mode 100644 index 600d2ca05..000000000 --- a/quill_native_bridge/quill_native_bridge_web/CHANGELOG.md +++ /dev/null @@ -1,17 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -## 0.0.1-dev.2 - -- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. - -## 0.0.1-dev.1 - -- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. -- Use [`quill_native_bridge_platform_interface`](https://pub.dev/packages/quill_native_bridge_platform_interface) instead of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge) - - -## 0.0.1-dev.0 - -- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_web/LICENSE b/quill_native_bridge/quill_native_bridge_web/LICENSE deleted file mode 100644 index e7ff73e1b..000000000 --- a/quill_native_bridge/quill_native_bridge_web/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_web/README.md b/quill_native_bridge/quill_native_bridge_web/README.md deleted file mode 100644 index 0ad1b56de..000000000 --- a/quill_native_bridge/quill_native_bridge_web/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# đŸĒļ Quill Native Bridge - -The web implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). - -## ⚙ī¸ Usage - -This package is endorsed, which means you can simply use `quill_native_bridge` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. - -However, if you import this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. - -## 📉 Note on breaking changes - -The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart b/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart deleted file mode 100644 index 6d8349400..000000000 --- a/quill_native_bridge/quill_native_bridge_web/lib/quill_native_bridge_web.dart +++ /dev/null @@ -1,144 +0,0 @@ -// This file is referenced by pubspec.yaml. If you plan on moving this file -// Make sure to update pubspec.yaml to the new location. - -import 'dart:js_interop'; - -import 'package:flutter/foundation.dart' show Uint8List, debugPrint; -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; -import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; -import 'package:web/web.dart'; - -import 'src/clipboard_api_support_unsafe.dart'; -import 'src/mime_types_constants.dart'; - -/// A web implementation of the [QuillNativeBridgePlatform]. -/// -/// **Highly Experimental** and can be removed. -/// -/// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: -/// -/// ```console -/// Assertion failed: "Platform interfaces must not be implemented with `implements`" -/// ``` -/// -/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) -/// and [QuillNativeBridgePlatform] for more details. -class QuillNativeBridgeWeb extends QuillNativeBridgePlatform { - QuillNativeBridgeWeb._(); - - static void registerWith(Registrar registrar) { - QuillNativeBridgePlatform.instance = QuillNativeBridgeWeb._(); - } - - @override - Future isSupported(QuillNativeBridgeFeature feature) async { - switch (feature) { - case QuillNativeBridgeFeature.isIOSSimulator: - return false; - case QuillNativeBridgeFeature.getClipboardHtml: - case QuillNativeBridgeFeature.copyHtmlToClipboard: - case QuillNativeBridgeFeature.copyImageToClipboard: - case QuillNativeBridgeFeature.getClipboardImage: - return isClipboardApiSupported; - case QuillNativeBridgeFeature.getClipboardGif: - return false; - // Without this default check, adding new item to the enum will be a breaking change - default: - throw UnimplementedError( - 'Checking if `${feature.name}` is supported on the web is not covered.', - ); - } - } - - @override - Future getClipboardHtml() async { - if (isClipbaordApiUnsupported) { - throw UnsupportedError( - 'Could not retrieve HTML from the clipboard.\n' - 'The Clipboard API is not supported on ${window.navigator.userAgent}.\n' - 'Should fallback to Clipboard events.', - ); - } - final clipboardItems = - (await window.navigator.clipboard.read().toDart).toDart; - for (final item in clipboardItems) { - if (item.types.toDart.contains(kHtmlMimeType.toJS)) { - final html = await item.getType(kHtmlMimeType).toDart; - return (await html.text().toDart).toDart; - } - } - return null; - } - - @override - Future copyHtmlToClipboard(String html) async { - if (isClipbaordApiUnsupported) { - throw UnsupportedError( - 'Could not copy HTML to the clipboard.\n' - 'The Clipboard API is not supported on ${window.navigator.userAgent}.\n' - 'Should fallback to Clipboard events.', - ); - } - final blob = Blob([html.toJS].toJS, BlobPropertyBag(type: kHtmlMimeType)); - final clipboardItem = ClipboardItem( - {kHtmlMimeType.toJS: blob}.jsify() as JSObject, - ); - await window.navigator.clipboard.write([clipboardItem].toJS).toDart; - } - - @override - Future copyImageToClipboard(Uint8List imageBytes) async { - if (isClipbaordApiUnsupported) { - throw UnsupportedError( - 'Could not copy image to the clipboard.\n' - 'The Clipboard API is not supported on ${window.navigator.userAgent}.\n' - 'Should fallback to Clipboard events.', - ); - } - final blob = Blob( - [imageBytes.toJS].toJS, - BlobPropertyBag(type: kImagePngMimeType), - ); - - final clipboardItem = ClipboardItem( - {kImagePngMimeType.toJS: blob}.jsify() as JSObject, - ); - - await window.navigator.clipboard.write([clipboardItem].toJS).toDart; - } - - @override - Future getClipboardImage() async { - if (isClipbaordApiUnsupported) { - throw UnsupportedError( - 'Could not retrieve image from the clipboard.\n' - 'The Clipboard API is not supported on ${window.navigator.userAgent}.\n' - 'Should fallback to Clipboard events.', - ); - } - final clipboardItems = - (await window.navigator.clipboard.read().toDart).toDart; - for (final item in clipboardItems) { - if (item.types.toDart.contains(kImagePngMimeType.toJS)) { - final blob = await item.getType(kImagePngMimeType).toDart; - final arrayBuffer = await blob.arrayBuffer().toDart; - return arrayBuffer.toDart.asUint8List(); - } - } - return null; - } - - @override - Future getClipboardGif() { - assert(() { - debugPrint( - 'Retrieving gif image from the clipboard is unsupported regardless of the browser.\n' - 'Refer to https://github.com/singerdmx/flutter-quill/issues/2229 for discussion.', - ); - return true; - }()); - throw UnsupportedError( - 'Retrieving gif image from the clipboard is unsupported regardless of the browser.', - ); - } -} diff --git a/quill_native_bridge/quill_native_bridge_web/lib/src/clipboard_api_support_unsafe.dart b/quill_native_bridge/quill_native_bridge_web/lib/src/clipboard_api_support_unsafe.dart deleted file mode 100644 index bc3971130..000000000 --- a/quill_native_bridge/quill_native_bridge_web/lib/src/clipboard_api_support_unsafe.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'dart:js_interop'; -import 'dart:js_interop_unsafe'; - -import 'package:web/web.dart'; - -// Should minimize the usage of [dart:js_interop_unsafe] when possible. -// Importing [dart:js_interop_unsafe] into it's own file -// to avoid accidentally using APIs from it. - -/// Verify if the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) -/// is supported and available. -/// -/// Can be `false` for some browsers (e.g. **Firefox**), fallback to -/// Clipboard events (e.g. [paste_event](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event)). -bool get isClipboardApiSupported => - window.navigator.getProperty('clipboard'.toJS) != null && - window.navigator.hasProperty('clipboard'.toJS).toDart; - -/// Negation of [isClipboardApiSupported] -bool get isClipbaordApiUnsupported => !isClipboardApiSupported; diff --git a/quill_native_bridge/quill_native_bridge_web/lib/src/mime_types_constants.dart b/quill_native_bridge/quill_native_bridge_web/lib/src/mime_types_constants.dart deleted file mode 100644 index 8a898618d..000000000 --- a/quill_native_bridge/quill_native_bridge_web/lib/src/mime_types_constants.dart +++ /dev/null @@ -1,2 +0,0 @@ -const String kHtmlMimeType = 'text/html'; -const String kImagePngMimeType = 'image/png'; diff --git a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml b/quill_native_bridge/quill_native_bridge_web/pubspec.yaml deleted file mode 100644 index c745040d3..000000000 --- a/quill_native_bridge/quill_native_bridge_web/pubspec.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: quill_native_bridge_web -description: "Web platform implementation of quill_native_bridge" -version: 0.0.1-dev.2 -homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web -repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web -issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ -documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_web - -environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' - -dependencies: - flutter: - sdk: flutter - flutter_web_plugins: - sdk: flutter - web: ^1.0.0 - quill_native_bridge_platform_interface: ^0.0.1-dev.2 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^4.0.0 - -flutter: - plugin: - implements: quill_native_bridge - platforms: - web: - pluginClass: QuillNativeBridgeWeb - fileName: quill_native_bridge_web.dart diff --git a/quill_native_bridge/quill_native_bridge_web/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_web/pubspec_overrides.yaml deleted file mode 100644 index a07f95b27..000000000 --- a/quill_native_bridge/quill_native_bridge_web/pubspec_overrides.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing -dependency_overrides: - quill_native_bridge_platform_interface: - path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_windows/CHANGELOG.md b/quill_native_bridge/quill_native_bridge_windows/CHANGELOG.md deleted file mode 100644 index eb3c50b72..000000000 --- a/quill_native_bridge/quill_native_bridge_windows/CHANGELOG.md +++ /dev/null @@ -1,12 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - - -## 0.0.1-dev.1 - -- Highly experimental changes in https://github.com/singerdmx/flutter-quill/pull/2230 (WIP). Not intended for public use as breaking changes will occur. Not stable yet. - -## 0.0.1-dev.0 - -- Initial experimental release. WIP in https://github.com/singerdmx/flutter-quill/pull/2230. Not intended for public use as breaking changes will occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_windows/LICENSE b/quill_native_bridge/quill_native_bridge_windows/LICENSE deleted file mode 100644 index e7ff73e1b..000000000 --- a/quill_native_bridge/quill_native_bridge_windows/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Flutter Quill project and open source 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/quill_native_bridge/quill_native_bridge_windows/README.md b/quill_native_bridge/quill_native_bridge_windows/README.md deleted file mode 100644 index 0b4cb4f10..000000000 --- a/quill_native_bridge/quill_native_bridge_windows/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# đŸĒļ Quill Native Bridge - -The Windows implementation of [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). - -## ⚙ī¸ Usage - -This package is endorsed, which means you can simply use `quill_native_bridge` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. - -However, if you import this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. - -## 📉 Note on breaking changes - -The `quill_native_bridge` is intended for internal use and exclusively for `flutter_quill`. Breaking changes may occur. \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart b/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart deleted file mode 100644 index ac6dc1bb3..000000000 --- a/quill_native_bridge/quill_native_bridge_windows/lib/quill_native_bridge_windows.dart +++ /dev/null @@ -1,196 +0,0 @@ -// This file is referenced by pubspec.yaml. If you plan on moving this file -// Make sure to update pubspec.yaml to the new location. - -import 'dart:ffi'; - -import 'package:ffi/ffi.dart'; -import 'package:quill_native_bridge_platform_interface/quill_native_bridge_platform_interface.dart'; -import 'package:win32/win32.dart'; - -import 'src/clipboard_html_format.dart'; -import 'src/html_cleaner.dart'; -import 'src/html_formatter.dart'; - -/// A Windows implementation of the [QuillNativeBridgePlatform]. -/// -/// **Highly Experimental** and can be removed. -/// -/// Should extends [QuillNativeBridgePlatform] and not implements it as error will arise: -/// -/// ```console -/// Assertion failed: "Platform interfaces must not be implemented with `implements`" -/// ``` -/// -/// See [Flutter #127396](https://github.com/flutter/flutter/issues/127396) -/// and [QuillNativeBridgePlatform] for more details. -/// ``` -class QuillNativeBridgeWindows extends QuillNativeBridgePlatform { - QuillNativeBridgeWindows._(); - - static void registerWith() { - QuillNativeBridgePlatform.instance = QuillNativeBridgeWindows._(); - } - - @override - Future isSupported(QuillNativeBridgeFeature feature) async => { - QuillNativeBridgeFeature.getClipboardHtml, - QuillNativeBridgeFeature.copyHtmlToClipboard, - }.contains(feature); - - // TODO: Cleanup this code here - - // TODO: Improve error handling by throwing exception - // instead of using assert, should have a proper way of handling - // errors regardless of this implementation. - - // TODO: Test Clipboard operations with other windows apps and - // see if this implementation causing issues - - // TODO: Might extract low-level implementation of the clipboard outside of this class - - /// Refer to [Windows GetClipboardData() docs](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclipboarddata) - @override - Future getClipboardHtml() async { - if (OpenClipboard(NULL) == FALSE) { - assert( - false, - 'Unknown error while opening the clipboard. Error code: ${GetLastError()}', - ); - return null; - } - - try { - final htmlFormatId = cfHtml; - - if (htmlFormatId == null) { - assert(false, 'Failed to register clipboard HTML format.'); - return null; - } - - if (IsClipboardFormatAvailable(htmlFormatId) == FALSE) { - return null; - } - - final clipboardDataHandle = GetClipboardData(htmlFormatId); - if (clipboardDataHandle == NULL) { - assert( - false, - 'Failed to get clipboard data. Error code: ${GetLastError()}', - ); - return null; - } - - final clipboardDataPointer = Pointer.fromAddress(clipboardDataHandle); - final lockedMemoryPointer = GlobalLock(clipboardDataPointer); - if (lockedMemoryPointer == nullptr) { - assert( - false, - 'Failed to lock global memory. Error code: ${GetLastError()}', - ); - return null; - } - - final windowsHtmlWithMetadata = - lockedMemoryPointer.cast().toDartString(); - GlobalUnlock(clipboardDataPointer); - - // Strip comments/headers at the start of the HTML as they can cause - // issues while parsing the HTML - - final cleanedHtml = - stripWindowsHtmlDescriptionHeaders(windowsHtmlWithMetadata); - - return cleanedHtml; - } finally { - CloseClipboard(); - } - } - - /// Refer to [Windows SetClipboardData() docs](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclipboarddata) - @override - Future copyHtmlToClipboard(String html) async { - if (OpenClipboard(NULL) == FALSE) { - assert( - false, - 'Unknown error while opening the clipboard. Error code: ${GetLastError()}', - ); - return; - } - - final windowsClipboardHtml = constructWindowsHtmlDescriptionHeaders(html); - final htmlPointer = windowsClipboardHtml.toNativeUtf8(); - - try { - if (EmptyClipboard() == FALSE) { - assert( - false, - 'Failed to empty the clipboard. Error code: ${GetLastError()}', - ); - return; - } - - final htmlFormatId = cfHtml; - - if (htmlFormatId == null) { - assert( - false, - 'Failed to register clipboard HTML format. Error code: ${GetLastError()}', - ); - return; - } - - final unitSize = sizeOf(); - final htmlSize = (htmlPointer.length + 1) * unitSize; - - final clipboardMemoryHandle = - GlobalAlloc(GLOBAL_ALLOC_FLAGS.GMEM_MOVEABLE, htmlSize); - if (clipboardMemoryHandle == nullptr) { - assert( - false, - 'Failed to allocate memory for the clipboard content. Error code: ${GetLastError()}', - ); - return; - } - - final lockedMemoryPointer = GlobalLock(clipboardMemoryHandle); - if (lockedMemoryPointer == nullptr) { - GlobalFree(clipboardMemoryHandle); - assert( - false, - 'Failed to lock global memory. Error code: ${GetLastError()}', - ); - return; - } - - final targetMemoryPointer = lockedMemoryPointer.cast(); - - final sourcePointer = htmlPointer.cast(); - - // Copy HTML data byte by byte - for (var i = 0; i < htmlPointer.length; i++) { - targetMemoryPointer[i] = (sourcePointer + i).value; - } - - // Add a null terminator for HTML (necessary for proper string handling) - (targetMemoryPointer + htmlPointer.length).value = NULL; - - // According to Windows docs in https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclipboarddata#parameters - // Should not call GlobalFree() when SetClipboardData() success - // as the Windows clipboard takes ownership of the memory. - - GlobalUnlock(clipboardMemoryHandle); - - if (SetClipboardData(htmlFormatId, clipboardMemoryHandle.address) == - NULL) { - GlobalFree(clipboardMemoryHandle); - assert( - false, - 'Failed to set the clipboard data: ${GetLastError()}', - ); - } - } finally { - CloseClipboard(); - calloc.free(htmlPointer); - } - } -} diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/src/clipboard_html_format.dart b/quill_native_bridge/quill_native_bridge_windows/lib/src/clipboard_html_format.dart deleted file mode 100644 index bb218ec34..000000000 --- a/quill_native_bridge/quill_native_bridge_windows/lib/src/clipboard_html_format.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:win32/win32.dart'; - -import '../quill_native_bridge_windows.dart'; - -/// From [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format). -const _kHtmlFormatName = 'HTML Format'; - -int? _cfHtml; - -extension ClipboardHtmlFormatExt on QuillNativeBridgeWindows { - /// The id of [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format). - /// - /// Registers the format if it doesn't exist; returns the existing format. - /// Returns `null` if an error occurs. - int? get cfHtml { - _cfHtml ??= _registerHtmlFormat(); - return _cfHtml; - } - - int? _registerHtmlFormat() { - final htmlFormatPointer = TEXT(_kHtmlFormatName); - final htmlFormatId = RegisterClipboardFormat(htmlFormatPointer); - free(htmlFormatPointer); - - if (htmlFormatId == NULL) { - // When error occurs - return null; - } - return htmlFormatId; - } -} diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart b/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart deleted file mode 100644 index edd8a554f..000000000 --- a/quill_native_bridge/quill_native_bridge_windows/lib/src/html_cleaner.dart +++ /dev/null @@ -1,76 +0,0 @@ -// Used to clean and remove HTML description headers -// when retrieving HTML from the windows clipboard which are -// usually not needed and can cause issues when parsing the HTML. - -/// See [HTML Clipboard Description Headers](https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format#description-headers-and-offsets) -/// for more details. -const _kWindowsDescriptionHeaders = { - 'Version', - 'StartHTML', - 'EndHTML', - 'StartFragment', - 'EndFragment', - 'StartSelection', - 'EndSelection' -}; - -/// Remove the leading description headers from Windows clipboard HTML. -/// -/// This function targets specific metadata keys that precede the actual HTML content: -/// - `Version` -/// - `StartHTML` -/// - `EndHTML` -/// - `StartFragment` -/// - `EndFragment` -/// - `StartSelection` -/// - `EndSelection` -/// -/// These keys are not valid HTML and should be removed for proper parsing. -/// -/// This function assumes that the metadata block appears before -/// the actual HTML content and that it's formatted consistently with keys -/// followed by values. -/// -/// [html] The HTML content retrieved from the clipboard, which includes the metadata. -/// -/// Example of the original (dirty) HTML: -/// -/// ```html -/// Version:0.9 -/// StartHTML:0000000105 -/// EndHTML:0000000634 -/// StartFragment:0000000141 -/// EndFragment:0000000598 -/// -/// -///
Example HTML
-/// -/// -/// ``` -/// -/// Refer to [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format) -/// for details. -String stripWindowsHtmlDescriptionHeaders(String html) { - // TODO: Using string indices can be more efficient and more minimal - // to implement using index indexOf() and substring() - // Can contains dirty lines - final lines = html.split('\n'); - - final cleanedLines = [...lines]; - - for (final line in lines) { - // Stop processing when reaching the start of actual HTML content - if (line.toLowerCase().startsWith('')) { - break; - } - - final isWindowsHtmlDescriptionHeader = _kWindowsDescriptionHeaders - .any((metadataKey) => line.startsWith('$metadataKey:')); - if (isWindowsHtmlDescriptionHeader) { - cleanedLines.remove(line); - continue; - } - } - - return cleanedLines.join('\n'); -} diff --git a/quill_native_bridge/quill_native_bridge_windows/lib/src/html_formatter.dart b/quill_native_bridge/quill_native_bridge_windows/lib/src/html_formatter.dart deleted file mode 100644 index 4af40133e..000000000 --- a/quill_native_bridge/quill_native_bridge_windows/lib/src/html_formatter.dart +++ /dev/null @@ -1,116 +0,0 @@ -// Used to convert a HTML to format that the Windows Clipboard expect. - -const _kStartBodyTag = ''; -const _kEndBodyTag = ''; - -const _kStartHtmlTag = ''; -const _kEndHtmlTag = ''; - -const _kStartFragmentComment = ''; -const _kEndFragmentComment = ''; - -/// Provide a header with additional information to the [html] -/// for the HTML to be suitable for storing in the Windows Clipboard. -/// Windows clipboard expect this additional information is set before -/// copying [html] to the clipboard. -/// -/// See [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format) -/// for more details. -String constructWindowsHtmlDescriptionHeaders(String html) { - final htmlBodyContent = _extractBodyContent(html); - - // TODO: Handle the case where the HTML already have those headers (not common) - - // Version `1.0` is supported on Windows 10 20H2 and newer versions. - // `StartSelection` and `EndSelection` are optional. - // See https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format#description-headers-and-offsets - const version = '1.0'; - - /// HTML template containing placeholders for invalid header values; will be replaced in a separate variable. - final invalidHeaderHtmlTemplate = ''' -Version:$version -StartHTML:0001 -EndHTML:0002 -StartFragment:0003 -EndFragment:0004 -$_kStartFragmentComment$htmlBodyContent$_kEndFragmentComment -'''; - - // Important: Should calculate offsets after adding the headers (StartHTML, EndHTML, etc.) - - // Windows expect those to be -1 if no context provided - // https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format#description-headers-and-offsets - - final startHtmlPos = invalidHeaderHtmlTemplate.indexOf(_kStartHtmlTag) + - _kStartHtmlTag.length; // Start After - final endHtmlPos = - invalidHeaderHtmlTemplate.indexOf(_kEndHtmlTag); // End before - - final startFragment = - invalidHeaderHtmlTemplate.indexOf(_kStartFragmentComment) + - _kStartFragmentComment.length; // Start after - final endFragment = invalidHeaderHtmlTemplate - .indexOf(_kEndFragmentComment); // End before - - // Important: Those invalid values should remain with the same length - // in the template as they used to calculate the offsets - // even if they have different values, otherwise will - // cause a bug as the offsets will be invalid. - return invalidHeaderHtmlTemplate - .replaceFirst('0001', _formatPosition(startHtmlPos)) - .replaceFirst('0002', _formatPosition(endHtmlPos)) - .replaceFirst('0003', _formatPosition(startFragment)) - .replaceFirst('0004', _formatPosition(endFragment)); -} - -/// Formats a given position to a 4-digit zero-padded string. -/// -/// This is necessary because Windows clipboard requires the positions to -/// be formatted in a specific way, using 4 digits, with leading zeros -/// if necessary. For example, the position `121` would be formatted as -/// `00121`. -/// -/// [position] The offset position (in bytes) to format. -/// -/// Returns: A string representing the formatted position. -/// -/// See [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format) -/// for more details. -String _formatPosition(int position) { - if (position == -1) { - return position.toString(); - } - return position.toString().padLeft(4, '0'); -} - -/// Extracts the content within ... tags from the provided [html]. -/// -/// If `` and `` tags are found, the content between them is returned. -/// If `` and `` tags are not present, the entire HTML string is returned, -/// trimmed of leading and trailing whitespace. -/// -/// Example: -/// ```dart -/// String html = 'Hello World'; -/// String bodyContent = _extractBodyContent(html); -/// print(bodyContent); // Output: 'Hello World' -/// ``` -/// -/// **Note**: -/// This operation is case-insensitive and will treat `` and `` as equivalent. -String _extractBodyContent(String html) { - final startBodyIndex = html.toLowerCase().indexOf(_kStartBodyTag); - final endBodyIndex = html.toLowerCase().indexOf(_kEndBodyTag); - - final bodyTagFound = startBodyIndex != -1 && endBodyIndex != -1; - if (bodyTagFound) { - // Extract the content inside HTML Content - final bodyContentStartIndex = startBodyIndex + _kStartBodyTag.length; - final bodyContent = - html.substring(bodyContentStartIndex, endBodyIndex).trim(); - return bodyContent; - } - - // No with found - return html.trim(); -} diff --git a/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml b/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml deleted file mode 100644 index b77e84b83..000000000 --- a/quill_native_bridge/quill_native_bridge_windows/pubspec.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: quill_native_bridge_windows -description: "Windows implementation of the quill_native_bridge plugin." -version: 0.0.1-dev.1 -homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_windows -repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_windows -issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ -documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/quill_native_bridge_windows - -environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' - -dependencies: - flutter: - sdk: flutter - quill_native_bridge_platform_interface: ^0.0.1-dev.2 - win32: ^5.5.4 - ffi: ^2.1.3 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^4.0.0 - -flutter: - plugin: - implements: quill_native_bridge - platforms: - windows: - pluginClass: none - dartPluginClass: QuillNativeBridgeWindows - fileName: quill_native_bridge_windows.dart diff --git a/quill_native_bridge/quill_native_bridge_windows/pubspec_overrides.yaml b/quill_native_bridge/quill_native_bridge_windows/pubspec_overrides.yaml deleted file mode 100644 index a07f95b27..000000000 --- a/quill_native_bridge/quill_native_bridge_windows/pubspec_overrides.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# TODO: Remove this file completely once https://github.com/singerdmx/flutter-quill/pull/2230 is complete before publishing -dependency_overrides: - quill_native_bridge_platform_interface: - path: ../quill_native_bridge_platform_interface \ No newline at end of file diff --git a/quill_native_bridge/quill_native_bridge_windows/test/html_cleaner_test.dart b/quill_native_bridge/quill_native_bridge_windows/test/html_cleaner_test.dart deleted file mode 100644 index 81140502e..000000000 --- a/quill_native_bridge/quill_native_bridge_windows/test/html_cleaner_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:quill_native_bridge_windows/src/html_cleaner.dart'; - -void main() { - group('stripWin32HtmlDescription', () { - test( - 'should remove windows metadata block from clipboard HTML', - () { - const windowsClipboardHtmlExample = ''' -Version:0.9 -StartHTML:0000000105 -EndHTML:0000000634 -StartFragment:0000000141 -EndFragment:0000000598 - - -
      return null;
- - -'''; - const expectedHtml = ''' - - -
      return null;
- - -'''; - final strippedHtml = - stripWindowsHtmlDescriptionHeaders(windowsClipboardHtmlExample); - expect( - strippedHtml, - expectedHtml, - ); - expect(strippedHtml.trim(), startsWith('')); - expect(strippedHtml.trim(), endsWith('')); - }, - ); - - test('should return original HTML if no metadata is found', () { - const cleanHtml = ''' - - -
Some clean HTML content
- -'''; - - expect(stripWindowsHtmlDescriptionHeaders(cleanHtml), equals(cleanHtml)); - }); - }); -} diff --git a/quill_native_bridge/quill_native_bridge_windows/test/html_formatter_test.dart b/quill_native_bridge/quill_native_bridge_windows/test/html_formatter_test.dart deleted file mode 100644 index 6a4a817cb..000000000 --- a/quill_native_bridge/quill_native_bridge_windows/test/html_formatter_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:quill_native_bridge_windows/src/html_formatter.dart'; - -void main() { - group('constructWindowsHtmlDescriptionHeaders', () { - test('should include windows descirption headers', () { - const htmlInput = - 'This is normal. This is bold. This is bold italic. This is italic.'; - const expectedWindowsHtml = ''' -Version:1.0 -StartHTML:0082 -EndHTML:0220 -StartFragment:0102 -EndFragment:0202 -This is normal. This is bold. This is bold italic. This is italic. -'''; - expect( - constructWindowsHtmlDescriptionHeaders(htmlInput), - expectedWindowsHtml, - ); - }); - }); -} From c0f4e1a383e087a11de9b806d3d70072fe71bc1e Mon Sep 17 00:00:00 2001 From: Ellet Date: Wed, 16 Oct 2024 00:09:18 +0300 Subject: [PATCH 86/90] docs(readme): improve the 'Rich Text Paste' section, drop the TODO for providing a way to customize the paste behavior and customize the clipboard operations --- README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8a6a9936d..7ac721aa7 100644 --- a/README.md +++ b/README.md @@ -347,17 +347,12 @@ for discussion. ## 📝 Rich Text Paste -The Rich Text Pasting feature requires platform code to access HTML from -the system clipboard, [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge) -is a plugin to provide this functionality. - -Rich clipboard operations are currently experimental and might be removed in future releases. - - +This feature allows the user to paste the content copied from other apps into the editor as rich text. +The plugin [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge) provides access to the system Clipboard. > [!IMPORTANT] > Currently this feature is not supported on the web. -> [Issue #1998](https://github.com/singerdmx/flutter-quill/issues/1998) and [Issue #2220](https://github.com/singerdmx/flutter-quill/issues/2220) +> See [issue #1998](https://github.com/singerdmx/flutter-quill/issues/1998) and [issue #2220](https://github.com/singerdmx/flutter-quill/issues/2220) for more details ## ✂ī¸ Shortcut events From 414f5a1857c09e5ed41b4d970f65708623c2fcba Mon Sep 17 00:00:00 2001 From: Ellet Date: Wed, 16 Oct 2024 00:41:17 +0300 Subject: [PATCH 87/90] chore: update deprecation message of FlutterQuillExtensions.useSuperClipboardPlugin() to reflect the change --- flutter_quill_extensions/lib/flutter_quill_extensions.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/flutter_quill_extensions/lib/flutter_quill_extensions.dart b/flutter_quill_extensions/lib/flutter_quill_extensions.dart index d6b6f2a77..e36dce9e1 100644 --- a/flutter_quill_extensions/lib/flutter_quill_extensions.dart +++ b/flutter_quill_extensions/lib/flutter_quill_extensions.dart @@ -64,8 +64,11 @@ class FlutterQuillExtensions { /// to allow `flutter_quill` package to use `super_clipboard` plugin /// to support rich text features, gif and images. @Deprecated( - 'Should not be used anymore as super_clipboard will moved outside of flutter_quill_extensions soon.\n' - 'A replacement is being made in https://github.com/singerdmx/flutter-quill/pull/2230', + 'The functionality of super_clipboard is now built-in in recent versions of flutter_quill.\n' + 'To migrate, remove this function call and see ' + 'https://pub.dev/packages/quill_native_bridge#-platform-configuration (optional for copying images on Android) to use quill_native_bridge implementation (the new default).\n' + 'Or if you want to use super_clipboard implementation (support might discontinued in newer versions), use the package https://pub.dev/packages/quill_super_clipboard\n' + 'See https://github.com/singerdmx/flutter-quill/pull/2230 for more details.', ) @experimental static void useSuperClipboardPlugin() { From 3401bac950a6800540dcd358de4940ac5aca433e Mon Sep 17 00:00:00 2001 From: Ellet Date: Wed, 16 Oct 2024 01:55:50 +0300 Subject: [PATCH 88/90] chore(example): remove commnet of deprecated method FlutterQuillExtensions.useSuperClipboardPlugin() --- example/lib/main.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 4685639ef..27e107671 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -19,8 +19,6 @@ import 'screens/settings/widgets/settings_screen.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - // TODO: https://github.com/singerdmx/flutter-quill/pull/2230 and related issues - // FlutterQuillExtensions.useSuperClipboardPlugin(); runApp(const MyApp()); } From 1c33424cbaa835aae608e5268e3a22a12896405f Mon Sep 17 00:00:00 2001 From: Ellet Date: Wed, 16 Oct 2024 02:00:27 +0300 Subject: [PATCH 89/90] chore: rename ClipboardService.copyImageToClipboard() to ClipboardService.copyImage() (non-breaking change) --- .../lib/src/editor/image/image_menu.dart | 3 +-- .../clipboard/super_clipboard_service.dart | 2 +- .../clipboard/clipboard_service.dart | 5 ++--- .../clipboard/default_clipboard_service.dart | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/flutter_quill_extensions/lib/src/editor/image/image_menu.dart b/flutter_quill_extensions/lib/src/editor/image/image_menu.dart index 32e5cae5b..6f64c4e5f 100644 --- a/flutter_quill_extensions/lib/src/editor/image/image_menu.dart +++ b/flutter_quill_extensions/lib/src/editor/image/image_menu.dart @@ -99,8 +99,7 @@ class ImageOptionsMenu extends StatelessWidget { final imageBytes = await _loadImageBytesFromImageProvider(); if (imageBytes != null) { - await ClipboardServiceProvider.instance - .copyImageToClipboard(imageBytes); + await ClipboardServiceProvider.instance.copyImage(imageBytes); } }, ), diff --git a/flutter_quill_extensions/lib/src/editor_toolbar_controller_shared/clipboard/super_clipboard_service.dart b/flutter_quill_extensions/lib/src/editor_toolbar_controller_shared/clipboard/super_clipboard_service.dart index e144f01c1..51466ff61 100644 --- a/flutter_quill_extensions/lib/src/editor_toolbar_controller_shared/clipboard/super_clipboard_service.dart +++ b/flutter_quill_extensions/lib/src/editor_toolbar_controller_shared/clipboard/super_clipboard_service.dart @@ -122,7 +122,7 @@ class SuperClipboardService extends ClipboardService { } @override - Future copyImageToClipboard(Uint8List imageBytes) async { + Future copyImage(Uint8List imageBytes) async { final clipboard = SystemClipboard.instance; if (clipboard == null) { return; diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart index 3f18a409a..46e6c3733 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/clipboard_service.dart @@ -21,9 +21,8 @@ abstract class ClipboardService { /// Return Gif from the Clipboard. Future getGifFile(); - // TODO: The `Clipboard` in `copyImageToClipboard` can be redundant - /// Copy [imageBytes] to the system clipboard to paste on other apps. - Future copyImageToClipboard(Uint8List imageBytes); + /// Copy an image to the system clipboard to paste it on other apps. + Future copyImage(Uint8List imageBytes); /// If the Clipboard is not empty or has something to paste Future get hasClipboardContent async { diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart index bec75d701..f4dd4debc 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart @@ -30,7 +30,7 @@ class DefaultClipboardService extends ClipboardService { } @override - Future copyImageToClipboard(Uint8List imageBytes) async { + Future copyImage(Uint8List imageBytes) async { if (!(await QuillNativeBridge.isSupported( QuillNativeBridgeFeature.copyImageToClipboard))) { return; From 6b46df4572b7334e2c15fb8b1bf166598ae52cfa Mon Sep 17 00:00:00 2001 From: Ellet Date: Wed, 16 Oct 2024 17:44:26 +0300 Subject: [PATCH 90/90] chore: add a link to a TODO in DefaultClipboardService._getClipboardFile() --- .../clipboard/default_clipboard_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart index f4dd4debc..21db10cdd 100644 --- a/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart +++ b/lib/src/editor_toolbar_controller_shared/clipboard/default_clipboard_service.dart @@ -49,7 +49,7 @@ class DefaultClipboardService extends ClipboardService { Future _getClipboardFile({required String fileExtension}) async { if (kIsWeb) { - // TODO: Can't read file with dart:io on the Web + // TODO: Can't read file with dart:io on the Web (See related https://github.com/FlutterQuill/quill-native-bridge/issues/6) return null; } final filePaths = await QuillNativeBridge.getClipboardFiles();