Skip to content

Commit

Permalink
Refactor(plugins)!: move super clipboard to extensions package (#1914)
Browse files Browse the repository at this point in the history
* fix(scripts): add execution permission for scripts/pub_get.sh

* chore(plugins)!: move super_clipboard plugin to be part of flutter_quill_extensions

* docs: add a comment for _pasteHtml() function in QuillController

* docs(extensions-package): update usage section to describe HTML and Markdown clipboard paste
  • Loading branch information
EchoEllet authored Jun 13, 2024
1 parent 784aee3 commit 743c829
Show file tree
Hide file tree
Showing 18 changed files with 364 additions and 117 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,11 @@ Before using the package, we must inform you the package use the following plugi
url_launcher
flutter_keyboard_visibility
device_info_plus
super_clipboard
```

All of them doesn't require any platform specific setup, except [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 then you must setup it, open the page in pub.dev and read the `README.md` to get the instructions.
All of them doesn't require any platform specific setup.

The minSdkVersion is `23` as `super_clipboard` requires it
> 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 features, support pasting images, gif files, take a look at `flutter_quill_extensions` Readme.

## Usage

Expand Down
2 changes: 1 addition & 1 deletion example/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ linter:
annotate_overrides: true
avoid_empty_else: true
avoid_escaping_inner_quotes: true
avoid_print: false
avoid_print: true
avoid_redundant_argument_values: false
avoid_types_on_closure_parameters: true
avoid_void_async: true
Expand Down
5 changes: 5 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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'
Expand All @@ -29,6 +30,7 @@ void main() async {
? HydratedStorage.webStorageDirectory
: await getApplicationDocumentsDirectory(),
);
FlutterQuillExtensions.useSuperClipboardPlugin();
runApp(const MyApp());
}

Expand Down Expand Up @@ -69,6 +71,9 @@ class MyApp extends StatelessWidget {
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
// Uncomment this line to use provide flutter quill localizations
// in your widgets app, otherwise the quill widgets will provide it
// internally:
// FlutterQuillLocalizations.delegate,
],
supportedLocales: FlutterQuillLocalizations.supportedLocales,
Expand Down
2 changes: 1 addition & 1 deletion example/lib/screens/home/widgets/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ class HomeScreen extends StatelessWidget {
),
);
} catch (e) {
print(
debugPrint(
'Error while loading json delta file: ${e.toString()}',
);
scaffoldMessenger.showText(
Expand Down
5 changes: 3 additions & 2 deletions example/lib/screens/quill/my_quill_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ class MyQuillEditor extends StatelessWidget {
return null;
}
// We will save it to system temporary files
final newFileName = '${DateTime.now().toIso8601String()}.png';
final newFileName =
'imageFile-${DateTime.now().toIso8601String()}.png';
final newPath = path.join(
io.Directory.systemTemp.path,
newFileName,
Expand All @@ -83,7 +84,7 @@ class MyQuillEditor extends StatelessWidget {
return null;
}
// We will save it to system temporary files
final newFileName = '${DateTime.now().toIso8601String()}.gif';
final newFileName = 'gifFile-${DateTime.now().toIso8601String()}.gif';
final newPath = path.join(
io.Directory.systemTemp.path,
newFileName,
Expand Down
2 changes: 1 addition & 1 deletion example/lib/screens/quill/quill_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class _QuillScreenState extends State<QuillScreen> {
IconButton(
tooltip: 'Print to log',
onPressed: () {
print(
debugPrint(
jsonEncode(_controller.document.toDelta().toJson()),
);
ScaffoldMessenger.of(context).showText(
Expand Down
94 changes: 55 additions & 39 deletions flutter_quill_extensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ to support embedding widgets like images, formulas, videos, and more.
- [Installation](#installation)
- [Platform Specific Configurations](#platform-specific-configurations)
- [Usage](#usage)
- [Embed Blocks](#embed-blocks)
- [Configurations](#configurations)
- [Embed Blocks](#embed-blocks)
- [Element properties](#element-properties)
- [Custom Element properties](#custom-element-properties)
- [Image Assets](#image-assets)
Expand Down Expand Up @@ -51,52 +52,68 @@ dependencies:
## Platform Specific Configurations
The package use the following plugins:
1. [`gal`](https://github.com/natsuk4ze/) plugin to save images.
For this to work, you need to add the appropriate configurations
See <https://github.com/natsuk4ze/gal#-get-started> to add the needed lines.
1. [`image_picker`](https://pub.dev/packages/image_picker) plugin for picking images so please make sure to follow the instructions
2. [youtube_player_flutter](https://pub.dev/packages/youtube_player_flutter) plugin which uses [flutter_inappwebview](https://pub.dev/packages/flutter_inappwebview) which has requirement on web, please follow this [link](https://pub.dev/packages/flutter_inappwebview#installation) in order to setup the support for web
3. [image_picker](https://pub.dev/packages/image_picker) which also
requires some configurations, follow this [link](https://pub.dev/packages/image_picker#installation). It's needed for Android, iOS, and macOS, we must inform you that you can't pick photos using the camera on a desktop so make sure to handle that if you plan on adding support for the desktop, this may change in the future, and for more info follow this [link](https://pub.dev/packages/image_picker#windows-macos-and-linux)
4. [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 then you must setup it, open the page in pub.dev and read the `README.md` or click on this [link](https://pub.dev/packages/super_clipboard#android-support) to get the instructions.

The minSdkVersion is `23` as `super_clipboard` requires it


> For loading the image from the internet <br> <br>
> **Android**: you need to add permissions in `AndroidManifest.xml`, Follow this [Android Guide](https://developer.android.com/training/basics/network-ops/connecting) or [Flutter Networking](https://docs.flutter.dev/data-and-backend/networking#android) for more info, the internet permission is included by default only for debugging so you need to follow this link to add it in the release version too. you should allow loading images and videos only for the `https` protocol but if you want http too then you need to configure your Android application to accept `http` in the release mode, follow this [Android Cleartext / Plaintext HTTP](https://developer.android.com/privacy-and-security/risks/cleartext) page for more info. <br> <br>
> **macOS**: you need to include a key in your `Info.plist`, follow this [link](https://docs.flutter.dev/data-and-backend/networking#macos) to add the required configurations
>
> 1. We are using the [`gal`](https://github.com/natsuk4ze/) plugin to save images.
> For this to work, you need to add the appropriate configurations
> See <https://github.com/natsuk4ze/gal#-get-started> to add the needed lines.
>
> 2. We also use [`image_picker`](https://pub.dev/packages/image_picker) plugin for picking images so please make sure to follow the instructions
>
> 3. We are using [youtube_player_flutter](https://pub.dev/packages/youtube_player_flutter) plugin which uses [flutter_inappwebview](https://pub.dev/packages/flutter_inappwebview) which has requirement on web, please follow this [link](https://pub.dev/packages/flutter_inappwebview#installation) in order to setup the support for web
> 4. For loading the image from the internet, we need the internet permission
> 1. For Android, you need to add some permissions in `AndroidManifest.xml`, Please follow this [link](https://developer.android.com/training/basics/network-ops/connecting) for more info, the internet permission is included by default only for debugging so you need to follow this link to add it in the release version too. you should allow loading images and videos only for the `https` protocol but if you want http too then you need to configure your Android application to accept `http` in the release mode, follow this [link](https://stackoverflow.com/questions/45940861/android-8-cleartext-http-traffic-not-permitted) for more info.
> 2. For macOS, you also need to include a key in your `Info.plist`, please follow this [link](https://stackoverflow.com/a/61201081/18519412) to add the required configurations
>
> The extension package also uses [image_picker](https://pub.dev/packages/image_picker) which also
> requires some configurations, follow this [link](https://pub.dev/packages/image_picker#installation). It's needed for Android, iOS, and macOS, we must inform you that you can't pick photos using the camera on a desktop so make sure to handle that if you plan on adding support for the desktop, this may change in the future, and for more info follow this [link](https://pub.dev/packages/image_picker#windows-macos-and-linux) <br>
>

## Usage

Before starting to use this package you must follow the [setup](#installation)
Start using the package in 3 steps:

Set the `embedBuilders` and `embedToolbar` params in configurations of `QuillEditor` and `QuillToolbar` with the
values provided by this repository.
1. Besure to to follow the [Installation](#installation) section.
2. This package already include `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

**Quill Toolbar**:
```dart
QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.toolbarButtons(),
),
),
```
```dart
FlutterQuillExtensions.useSuperClipboardPlugin();
```

**Quill Editor**
```dart
Expanded(
child: QuillEditor.basic(
configurations: QuillEditorConfigurations(
embedBuilders: kIsWeb ? FlutterQuillEmbeds.editorWebBuilders() : FlutterQuillEmbeds.editorBuilders(),
`super_clipboard` is comprehensive plugin that provide many clipboard features for reading and writing of rich text, images and other formats.

Executing this function will allow `flutter_quill` to use modern rich text features to paste HTML and Markdown, support for Gif files, and other formats.

3. Set the `embedBuilders` and `embedToolbar` params in configurations of `QuillEditor` and `QuillToolbar` with the
values provided by this repository.

**Quill Toolbar**:
```dart
QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.toolbarButtons(),
),
),
),
)
```
```

**Quill Editor**
```dart
Expanded(
child: QuillEditor.basic(
configurations: QuillEditorConfigurations(
embedBuilders: kIsWeb ? FlutterQuillEmbeds.editorWebBuilders() : FlutterQuillEmbeds.editorBuilders(),
),
),
)
```

## Configurations

## Embed Blocks
### Embed Blocks

As of version [flutter_quill](https://pub.dev/packages/flutter_quill) 6.0, embed blocks are not provided by default as part of Flutter quill. Instead, it provides an interface for all the users to provide their implementations for embed blocks. Implementations for image, video, and formula embed blocks are proved in this package
As of version [flutter_quill](https://pub.dev/packages/flutter_quill) `6.0.x`, embed blocks are not provided by default as part of Flutter quill. Instead, it provides an interface for all the users to provide their implementations for embed blocks. Implementations for image, video, and formula embed blocks are proved in this package

The instructions for using the embed blocks are in the [Usage](#usage) section

Expand Down Expand Up @@ -236,9 +253,8 @@ OnDragDoneCallback get _onDragDone {
## Features
— Easy to use and customizable
- Has the option to use a custom image provider for the images
- Rich text, images and other formats
- Useful utilities and widgets
- Handle different errors
```

## Contributing
Expand Down
18 changes: 18 additions & 0 deletions flutter_quill_extensions/lib/flutter_quill_extensions.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
library flutter_quill_extensions;

// ignore: implementation_imports
import 'package:flutter_quill/src/services/clipboard/clipboard_service_provider.dart';
import 'package:meta/meta.dart' show immutable;

import 'services/clipboard/super_clipboard_service.dart';

export 'embeds/embed_types.dart';
export 'embeds/formula/toolbar/formula_button.dart';
export 'embeds/image/editor/image_embed.dart';
Expand All @@ -26,3 +32,15 @@ export 'models/config/video/editor/video_configurations.dart';
export 'models/config/video/editor/video_web_configurations.dart';
export 'models/config/video/toolbar/video_configurations.dart';
export 'utils/utils.dart';

@immutable
class FlutterQuillExtensions {
const FlutterQuillExtensions._();

/// Override default implementation of [ClipboardServiceProvider.instacne]
/// to allow `flutter_quill` package to use `super_clipboard` plugin
/// to support rich text features, gif and images.
static void useSuperClipboardPlugin() {
ClipboardServiceProvider.setInstance(SuperClipboardService());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import 'dart:async' show Completer;

import 'package:flutter/foundation.dart';
// ignore: implementation_imports
import 'package:flutter_quill/src/services/clipboard/clipboard_service.dart';
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
/// https://pub.dev/packages/super_clipboard#usage
SystemClipboard? _getSuperClipboard() {
return SystemClipboard.instance;
}

Future<bool> _canProvide({required DataFormat format}) async {
final clipboard = _getSuperClipboard();
if (clipboard == null) {
return false;
}
final reader = await clipboard.read();
return reader.canProvide(format);
}

Future<Uint8List> _provideFileAsBytes({required FileFormat format}) async {
final clipboard = _getSuperClipboard();
if (clipboard == null) {
// To avoid getting this exception, use _canProvide()
throw UnsupportedError(
'Clipboard API is not supported on this platform.',
);
}
final reader = await clipboard.read();
final completer = Completer<Uint8List>();

reader.getFile(format, (file) async {
final bytes = await file.readAll();
completer.complete(bytes);
});
final bytes = await completer.future;
return bytes;
}

/// According to super_clipboard docs, will return `null` if the value
/// is not available or the data is virtual (macOS and Windows)
Future<String?> _provideSimpleValueFormatAsString({
required SimpleValueFormat<String> format,
}) async {
final clipboard = _getSuperClipboard();
if (clipboard == null) {
// To avoid getting this exception, use _canProvide()
throw UnsupportedError(
'Clipboard API is not supported on this platform.',
);
}
final reader = await clipboard.read();
final value = await reader.readValue<String>(format);
return value;
}

/// 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<bool> canProvideImageFile() async {
final canProvidePngFile = await _canProvide(format: Formats.png);
if (canProvidePngFile) {
return true;
}
final canProvideJpegFile = await _canProvide(format: Formats.jpeg);
if (canProvideJpegFile) {
return true;
}
return false;
}

/// This will need to be updated if [canProvideImageFile] updated.
@override
Future<Uint8List> getImageFileAsBytes() async {
final canProvidePngFile = await _canProvide(format: Formats.png);
if (canProvidePngFile) {
return _provideFileAsBytes(format: Formats.png);
}
return _provideFileAsBytes(format: Formats.jpeg);
}

@override
Future<bool> canProvidePlainText() {
return _canProvide(format: Formats.plainText);
}

@override
Future<String?> getPlainText() {
return _provideSimpleValueFormatAsString(format: Formats.plainText);
}

@override
Future<bool> canProvideHtmlText() {
return _canProvide(format: Formats.htmlText);
}

@override
Future<String?> getHtmlText() {
return _provideSimpleValueFormatAsString(format: Formats.htmlText);
}

@override
Future<bool> canProvideGifFile() {
return _canProvide(format: Formats.gif);
}

@override
Future<Uint8List> getGifFileAsBytes() {
return _provideFileAsBytes(format: Formats.gif);
}

@override
Future<bool> canPaste() async {
final clipboard = _getSuperClipboard();
if (clipboard == null) {
return false;
}
final reader = await clipboard.read();
final availablePlatformFormats = reader.platformFormats;
return availablePlatformFormats.isNotEmpty;
}
}
19 changes: 19 additions & 0 deletions lib/src/services/clipboard/clipboard_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:flutter/foundation.dart';

/// An abstraction to make it easy to provide different implementations
@immutable
abstract class ClipboardService {
Future<bool> canProvideHtmlText();
Future<String?> getHtmlText();

Future<bool> canProvidePlainText();
Future<String?> getPlainText();

Future<bool> canProvideImageFile();
Future<Uint8List> getImageFileAsBytes();

Future<bool> canProvideGifFile();
Future<Uint8List> getGifFileAsBytes();

Future<bool> canPaste();
}
Loading

0 comments on commit 743c829

Please sign in to comment.