diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93ae689 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# 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/ +.flutter-plugins +.flutter-plugins-dependencies diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..24472f1 --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# 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: package diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..77b39e5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* initial release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..09707da --- /dev/null +++ b/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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3bf0d19 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# 🪶 Quill Super Clipboard + +A package for integrating the package [`super_clipboard`](https://pub.dev/packages/super_clipboard) with [`flutter_quill`](https://pub.dev/packages/flutter_quill) +to provide seamless access to the system clipboard for rich text operations. + +> **Caution** +> +> The support of this package might be discontinued in the future versions of [flutter_quill](https://pub.dev/packages/flutter_quill). + +## Usage + +To use the `super_clipboard` implementation with this package: + +```dart +import 'package:quill_super_clipboard/quill_super_clipboard.dart'; + +QuillSuperClipboard.use(); +``` + +> **Note** +> +> The default clipboard implementation for `flutter_quill` is now [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge). It provides full support for all system clipboard features used by `flutter_quill`. \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..448d91f --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,2 @@ +include: package:flutter_lints/flutter.yaml + diff --git a/lib/quill_super_clipboard.dart b/lib/quill_super_clipboard.dart new file mode 100644 index 0000000..776d49d --- /dev/null +++ b/lib/quill_super_clipboard.dart @@ -0,0 +1,29 @@ +/// A library for integrating the package [`super_clipboard`](https://pub.dev/packages/super_clipboard) with [`flutter_quill`](https://pub.dev/packages/flutter_quill) +/// to provide seamless access to the system clipboard for rich text operations. +@experimental +library; + +import 'package:flutter_quill/flutter_quill_internal.dart'; +import 'package:meta/meta.dart' show experimental; + +import 'src/super_clipboard_service.dart'; + +/// Provides integration of the [`super_clipboard`](https://pub.dev/packages/super_clipboard) +/// package with [`flutter_quill`](https://pub.dev/packages/flutter_quill) package +/// to the system clipboard for rich text operations. +@experimental +class QuillSuperClipboard { + /// Private constructor + QuillSuperClipboard._(); + + /// Use the [`super_clipboard`](https://pub.dev/packages/super_clipboard) + /// plugin as clipboard implementation for [`flutter_quill`](https://pub.dev/packages/flutter_quill) + /// to access the system clipboard for rich text operations. + /// + /// See also: [`quill_native_bridge`](https://pub.dev/packages/quill_native_bridge) + /// which is the default clipboard implementation for [flutter_quill](https://pub.dev/packages/flutter_quill). + @experimental + static void use() { + ClipboardServiceProvider.setInstance(SuperClipboardService()); + } +} diff --git a/lib/src/super_clipboard_service.dart b/lib/src/super_clipboard_service.dart new file mode 100644 index 0000000..e1672a1 --- /dev/null +++ b/lib/src/super_clipboard_service.dart @@ -0,0 +1,143 @@ +import 'dart:async' show Completer; +import 'dart:convert' show utf8; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_quill/flutter_quill_internal.dart'; +import 'package:meta/meta.dart' show experimental; + +import 'package:super_clipboard/super_clipboard.dart'; + +/// Implementation using the [super_clipboard](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 + SystemClipboard? _getSuperClipboard() { + return SystemClipboard.instance; + } + + SystemClipboard _getSuperClipboardOrThrow() { + final clipboard = _getSuperClipboard(); + if (clipboard == null) { + // To avoid getting this exception, use _canProvide() + throw UnsupportedError( + 'Clipboard API is not supported on this platform.', + ); + } + return clipboard; + } + + Future _canProvide({required DataFormat format}) async { + final clipboard = _getSuperClipboard(); + if (clipboard == null) { + return false; + } + final reader = await clipboard.read(); + return reader.canProvide(format); + } + + Future _provideFileAsBytes({ + required SimpleFileFormat format, + }) async { + final clipboard = _getSuperClipboardOrThrow(); + final reader = await clipboard.read(); + final completer = Completer(); + + reader.getFile( + format, + (file) async { + final bytes = await file.readAll(); + completer.complete(bytes); + }, + onError: completer.completeError, + ); + final bytes = await completer.future; + return bytes; + } + + Future _provideFileAsString({ + required SimpleFileFormat format, + }) async { + final fileBytes = await _provideFileAsBytes(format: format); + final fileText = utf8.decode(fileBytes); + return fileText; + } + + /// According to super_clipboard docs, will return `null` if the value + /// is not available or the data is virtual (macOS and Windows) + Future _provideSimpleValueFormatAsString({ + required SimpleValueFormat format, + }) async { + final clipboard = _getSuperClipboardOrThrow(); + final reader = await clipboard.read(); + final value = await reader.readValue(format); + return value; + } + + @override + Future getHtmlText() async { + if (!(await _canProvide(format: Formats.htmlText))) { + return null; + } + return _provideSimpleValueFormatAsString(format: Formats.htmlText); + } + + @override + Future getHtmlFile() async { + if (!(await _canProvide(format: Formats.htmlFile))) { + return null; + } + return await _provideFileAsString(format: Formats.htmlFile); + } + + @override + Future getGifFile() async { + if (!(await _canProvide(format: Formats.gif))) { + return null; + } + return await _provideFileAsBytes(format: Formats.gif); + } + + @override + Future getImageFile() async { + final canProvidePngFile = await _canProvide(format: Formats.png); + if (canProvidePngFile) { + return _provideFileAsBytes(format: Formats.png); + } + final canProvideJpegFile = await _canProvide(format: Formats.jpeg); + if (canProvideJpegFile) { + return _provideFileAsBytes(format: Formats.jpeg); + } + return null; + } + + @override + Future getMarkdownFile() async { + // Formats.md is for markdown files + if (!(await _canProvide(format: Formats.md))) { + return null; + } + return await _provideFileAsString(format: Formats.md); + } + + @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]); + } + + @override + Future get hasClipboardContent async { + final clipboard = _getSuperClipboard(); + if (clipboard == null) { + return false; + } + final reader = await clipboard.read(); + final availablePlatformFormats = reader.platformFormats; + return availablePlatformFormats.isNotEmpty; + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..4f6a0bf --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,26 @@ +name: quill_super_clipboard +description: "A package for integrating the package super_clipboard with flutter_quill to provide access to the system clipboard." +version: 0.0.1 +homepage: https://github.com/FlutterQuill/quill-super-clipboard +repository: https://github.com/FlutterQuill/quill-super-clipboard +issue_tracker: https://github.com/FlutterQuill/quill-super-clipboard/issues +documentation: https://github.com/FlutterQuill/quill-super-clipboard + +environment: + sdk: '>=3.0.0 <4.0.0' + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + super_clipboard: ^0.8.24 + # TODO: Publish the package and use latest version of flutter_quill once https://github.com/singerdmx/flutter-quill/pull/2230 is published + flutter_quill: + path: /Users/ellet/Developer/playground/framework_based/flutter/flutter-quill/ + meta: ^1.10.0 + +dev_dependencies: + test: ^1.25.8 + flutter_lints: ^5.0.0 + +flutter: diff --git a/test/quill_super_clipboard_test.dart b/test/quill_super_clipboard_test.dart new file mode 100644 index 0000000..57d7dbb --- /dev/null +++ b/test/quill_super_clipboard_test.dart @@ -0,0 +1,18 @@ +import 'package:flutter_quill/flutter_quill_internal.dart'; +import 'package:quill_super_clipboard/quill_super_clipboard.dart'; +import 'package:test/test.dart'; + +import 'package:quill_super_clipboard/src/super_clipboard_service.dart'; + +void main() { + test( + 'Calling QuillSuperClipboard.use() should set the instance to SuperClipboardService', + () { + QuillSuperClipboard.use(); + expect( + ClipboardServiceProvider.instance, + isA(), + ); + }, + ); +}