diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 004ca97c4..33b01067f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,6 +39,9 @@ jobs: - name: đŸ“Ļ Install flutter_quill_test dependencies run: flutter pub get -C flutter_quill_test + - name: đŸ“Ļ Install quill_native_bridge dependencies + run: flutter pub get -C quill_native_bridge + - name: 🔍 Run Flutter analysis run: flutter analyze diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8c166c273..436c53e5d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -107,3 +107,7 @@ jobs: - name: 📤 Publish flutter_quill_test run: flutter pub publish --force working-directory: ./flutter_quill_test/ + + - name: 📤 Publish quill_native_bridge + run: flutter pub publish --force + working-directory: ./quill_native_bridge/ diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 8f79b286f..99a591f26 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -216,7 +216,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a09b..8e3ca5dfe 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ isIOSSimulator() async { return false; } - final deviceInfo = DeviceInfoPlugin(); - - final osInfo = await deviceInfo.deviceInfo; - - if (osInfo is IosDeviceInfo) { - final iosInfo = osInfo; - return !iosInfo.isPhysicalDevice; - } - return false; + return await QuillNativeBridge.isIOSSimulator(); } // Mobile diff --git a/pubspec.yaml b/pubspec.yaml index 8b7c02487..fc7e0c14e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,7 +61,7 @@ dependencies: # Plugins url_launcher: ^6.2.4 flutter_keyboard_visibility: ^6.0.0 - device_info_plus: ^10.0.1 + quill_native_bridge: ^10.5.14 dev_dependencies: flutter_lints: ^4.0.0 diff --git a/pubspec_overrides.yaml.disabled b/pubspec_overrides.yaml.disabled index 94d344b00..abae944e4 100644 --- a/pubspec_overrides.yaml.disabled +++ b/pubspec_overrides.yaml.disabled @@ -2,4 +2,6 @@ dependency_overrides: flutter_quill_test: path: ./flutter_quill_test dart_quill_delta: - path: ./dart_quill_delta \ No newline at end of file + path: ./dart_quill_delta + quill_native_bridge: + path: ./quill_native_bridge \ No newline at end of file diff --git a/quill_native_bridge/.gitignore b/quill_native_bridge/.gitignore new file mode 100644 index 000000000..ac5aa9893 --- /dev/null +++ b/quill_native_bridge/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/quill_native_bridge/.metadata b/quill_native_bridge/.metadata new file mode 100644 index 000000000..5d0b67954 --- /dev/null +++ b/quill_native_bridge/.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: "5874a72aa4c779a02553007c47dacbefba2374dc" + channel: "stable" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + - platform: ios + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + + # 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/CHANGELOG.md b/quill_native_bridge/CHANGELOG.md new file mode 100644 index 000000000..f951c8674 --- /dev/null +++ b/quill_native_bridge/CHANGELOG.md @@ -0,0 +1,3 @@ +## 10.5.14 + +* TODO: This file will be updated soon by GitHub workflow. diff --git a/quill_native_bridge/LICENSE b/quill_native_bridge/LICENSE new file mode 100644 index 000000000..c084b28cc --- /dev/null +++ b/quill_native_bridge/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Flutter Quill Team + +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/README.md b/quill_native_bridge/README.md new file mode 100644 index 000000000..216dfc41e --- /dev/null +++ b/quill_native_bridge/README.md @@ -0,0 +1,6 @@ +# đŸĒļ Quill Native Bridge + +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 diff --git a/quill_native_bridge/analysis_options.yaml b/quill_native_bridge/analysis_options.yaml new file mode 100644 index 000000000..f9d394db2 --- /dev/null +++ b/quill_native_bridge/analysis_options.yaml @@ -0,0 +1,32 @@ +include: package:flutter_lints/flutter.yaml + +analyzer: +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/ios/.gitignore b/quill_native_bridge/ios/.gitignore new file mode 100644 index 000000000..034771fc9 --- /dev/null +++ b/quill_native_bridge/ios/.gitignore @@ -0,0 +1,38 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/ephemeral/ +/Flutter/flutter_export_environment.sh diff --git a/quill_native_bridge/ios/Assets/.gitkeep b/quill_native_bridge/ios/Assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift b/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift new file mode 100644 index 000000000..e5e9997ab --- /dev/null +++ b/quill_native_bridge/ios/Classes/QuillNativeBridgePlugin.swift @@ -0,0 +1,23 @@ +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 + default: + result(FlutterMethodNotImplemented) + } + } +} diff --git a/quill_native_bridge/ios/quill_native_bridge.podspec b/quill_native_bridge/ios/quill_native_bridge.podspec new file mode 100644 index 000000000..3e59e619e --- /dev/null +++ b/quill_native_bridge/ios/quill_native_bridge.podspec @@ -0,0 +1,29 @@ +# +# 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 '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_privacy' => ['Resources/PrivacyInfo.xcprivacy']} +end diff --git a/quill_native_bridge/lib/quill_native_bridge.dart b/quill_native_bridge/lib/quill_native_bridge.dart new file mode 100644 index 000000000..a14a6d271 --- /dev/null +++ b/quill_native_bridge/lib/quill_native_bridge.dart @@ -0,0 +1,14 @@ +library; + +import 'src/quill_native_bridge_platform_interface.dart'; + +class QuillNativeBridge { + 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] + /// is [TargetPlatform.iOS] and [kIsWeb] is `false`. + static Future isIOSSimulator() => + QuillNativeBridgePlatform.instance.isIOSSimulator(); +} 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 new file mode 100644 index 000000000..e4475cbc5 --- /dev/null +++ b/quill_native_bridge/lib/src/quill_native_bridge_method_channel.dart @@ -0,0 +1,32 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'quill_native_bridge_platform_interface.dart'; + +class MethodChannelQuillNativeBridge implements QuillNativeBridgePlatform { + @visibleForTesting + final methodChannel = const MethodChannel('quill_native_bridge'); + + @override + Future isIOSSimulator() async { + assert(() { + if (kIsWeb || defaultTargetPlatform != TargetPlatform.iOS) { + 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; + } +} 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 new file mode 100644 index 000000000..406c530e3 --- /dev/null +++ b/quill_native_bridge/lib/src/quill_native_bridge_platform_interface.dart @@ -0,0 +1,31 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'quill_native_bridge_method_channel.dart'; + +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; + } + + /// 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/pubspec.yaml b/quill_native_bridge/pubspec.yaml new file mode 100644 index 000000000..06b77bdb5 --- /dev/null +++ b/quill_native_bridge/pubspec.yaml @@ -0,0 +1,27 @@ +name: quill_native_bridge +description: "An internal plugin for flutter_quill package to access platform-specific APIs" +version: 10.5.14 +homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/ +repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/ +issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ +documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_native_bridge/ + +environment: + sdk: ^3.5.1 + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.1.8 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + +flutter: + plugin: + platforms: + ios: + pluginClass: QuillNativeBridgePlugin \ No newline at end of file diff --git a/quill_native_bridge/test/quill_native_bridge_method_channel_test.dart b/quill_native_bridge/test/quill_native_bridge_method_channel_test.dart new file mode 100644 index 000000000..d0e1bbd46 --- /dev/null +++ b/quill_native_bridge/test/quill_native_bridge_method_channel_test.dart @@ -0,0 +1,31 @@ +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'; + +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/test/quill_native_bridge_test.dart b/quill_native_bridge/test/quill_native_bridge_test.dart new file mode 100644 index 000000000..5ad8c969e --- /dev/null +++ b/quill_native_bridge/test/quill_native_bridge_test.dart @@ -0,0 +1,29 @@ +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/src/quill_native_bridge_platform_interface.dart'; + +class MockQuillNativeBridgePlatform + with MockPlatformInterfaceMixin + implements QuillNativeBridgePlatform { + @override + Future isIOSSimulator() async => false; +} + +void main() { + final initialPlatform = QuillNativeBridgePlatform.instance; + + test('$MethodChannelQuillNativeBridge is the default instance', () { + expect(initialPlatform, isInstanceOf()); + }); + + test('isIOSSimulator', () async { + final fakePlatform = MockQuillNativeBridgePlatform(); + QuillNativeBridgePlatform.instance = fakePlatform; + + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + expect(await QuillNativeBridge.isIOSSimulator(), false); + }); +} diff --git a/scripts/packages.dart b/scripts/packages.dart index 0ad09cd6e..229074f43 100644 --- a/scripts/packages.dart +++ b/scripts/packages.dart @@ -4,4 +4,5 @@ const repoPackages = [ './dart_quill_delta', './flutter_quill_extensions', './flutter_quill_test', + './quill_native_bridge' ];