quill_native_bridge 11.0.0 copy "quill_native_bridge: ^11.0.0" to clipboard
quill_native_bridge: ^11.0.0 copied to clipboard

An internal Flutter plugin for flutter_quill package to access platform-specific APIs

example/lib/main.dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:quill_native_bridge/quill_native_bridge.dart';

import 'album_name_input_dialog.dart';
import 'assets.dart';
import 'select_image_dialog.dart';

/// Creates a global instance of [QuillNativeBridge], allowing it to be overridden in tests.
QuillNativeBridge quillNativeBridge = QuillNativeBridge();

void main() => runApp(const MainApp());

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Quill Native Bridge'),
        ),
        body: const SingleChildScrollView(
          child: 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: () => _onButtonPressed(
            QuillNativeBridgeFeature.isIOSSimulator,
            context: context,
          ),
          label: const Text('Is iOS Simulator'),
          icon: const Icon(Icons.apple),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonPressed(
            QuillNativeBridgeFeature.getClipboardHtml,
            context: context,
          ),
          label: const Text('Get HTML from Clipboard'),
          icon: const Icon(Icons.html),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonPressed(
            QuillNativeBridgeFeature.copyHtmlToClipboard,
            context: context,
          ),
          label: const Text('Copy HTML to Clipboard'),
          icon: const Icon(Icons.copy),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonPressed(
            QuillNativeBridgeFeature.copyImageToClipboard,
            context: context,
          ),
          label: const Text('Copy Image to Clipboard'),
          icon: const Icon(Icons.copy),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonPressed(
            QuillNativeBridgeFeature.getClipboardImage,
            context: context,
          ),
          label: const Text('Retrieve Image from Clipboard'),
          icon: const Icon(Icons.image),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonPressed(
            QuillNativeBridgeFeature.getClipboardGif,
            context: context,
          ),
          label: const Text('Retrieve Gif from Clipboard'),
          icon: const Icon(Icons.gif),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonPressed(
            QuillNativeBridgeFeature.getClipboardFiles,
            context: context,
          ),
          label: const Text('Retrieve Files from Clipboard'),
          icon: const Icon(Icons.file_open),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonPressed(
            QuillNativeBridgeFeature.openGalleryApp,
            context: context,
          ),
          label: const Text('Open the gallery app'),
          icon: const Icon(Icons.photo_album),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonPressed(
            QuillNativeBridgeFeature.saveImageToGallery,
            context: context,
          ),
          label: const Text('Save image to gallery'),
          icon: const Icon(Icons.photo_album),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonPressed(
            QuillNativeBridgeFeature.saveImage,
            context: context,
          ),
          label: const Text('Save image'),
          icon: const Icon(Icons.image),
        ),
      ],
    );
  }
}

Future<void> _onButtonPressed(
  QuillNativeBridgeFeature feature, {
  required BuildContext context,
}) async {
  final scaffoldMessenger = ScaffoldMessenger.of(context);

  final isFeatureUnsupported = !(await quillNativeBridge.isSupported(feature));

  switch (feature) {
    case QuillNativeBridgeFeature.isIOSSimulator:
      if (isFeatureUnsupported) {
        scaffoldMessenger.showText(
          kIsWeb
              ? "Can't check if the device is an iOS simulator on the web."
              : 'Available only on iOS to determine if the device is a simulator.',
        );
        return;
      }
      final result = await quillNativeBridge.isIOSSimulator();
      scaffoldMessenger.showText(result
          ? "You're running the app on iOS simulator."
          : "You're running the app on a real iOS device.");
      break;
    case QuillNativeBridgeFeature.getClipboardHtml:
      if (isFeatureUnsupported) {
        scaffoldMessenger.showText(
          kIsWeb
              ? 'Retrieving HTML from the clipboard is currently not supported on the web.'
              : 'Retrieving HTML from the clipboard is currently 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(
          kIsWeb
              ? 'Copying HTML to the clipboard is not supported on the web.'
              : 'Copying HTML to the clipboard is not supported on ${defaultTargetPlatform.name}.',
        );
        return;
      }
      const html = '''
          <strong>Bold text</strong>
          <em>Italic text</em>
          <u>Underlined text</u>
          <span style="color:red;">Red text</span>
          <span style="background-color:yellow;">Highlighted text</span>
        ''';
      await quillNativeBridge.copyHtmlToClipboard(html);
      scaffoldMessenger.showText('HTML copied to the clipboard: $html');
      break;
    case QuillNativeBridgeFeature.copyImageToClipboard:
      if (isFeatureUnsupported) {
        scaffoldMessenger.showText(
          kIsWeb
              ? 'Copying an image to the clipboard is 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 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(
          kIsWeb
              ? '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(
          kIsWeb
              ? 'Retrieving a gif from the clipboard is not supported on web.'
              : 'Retrieving a gif from the clipboard is 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(
          kIsWeb
              ? 'Retrieving files from the clipboard is not supported on web.'
              : 'Retrieving files from the clipboard is 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;
    case QuillNativeBridgeFeature.openGalleryApp:
      if (isFeatureUnsupported) {
        scaffoldMessenger.showText(
          kIsWeb
              ? 'Opening the gallery app is not supported on web.'
              : 'Opening the gallery app is not supported on ${defaultTargetPlatform.name}.',
        );
        return;
      }
      await quillNativeBridge.openGalleryApp();
      break;
    case QuillNativeBridgeFeature.saveImageToGallery:
      if (isFeatureUnsupported) {
        scaffoldMessenger.showText(
          kIsWeb
              ? 'Saving an image to the gallery is not supported on web.'
              : 'Saving an image is not supported on ${defaultTargetPlatform.name}.',
        );
        return;
      }

      if (!context.mounted) return;
      final selectedImage = await showSelectImageDialog(context: context);

      if (selectedImage == null) {
        scaffoldMessenger.showText('Image save was canceled.');
        return;
      }
      final imageBytes = await loadAssetFile(selectedImage.assetPath);

      if (!context.mounted) return;
      final albumName = await showAlbumNameInputDialog(context: context);
      if (albumName == null) {
        scaffoldMessenger.showText('Image save was canceled.');
        return;
      }

      await quillNativeBridge.saveImageToGallery(
        imageBytes,
        options: GalleryImageSaveOptions(
          name: selectedImage.description,
          fileExtension: selectedImage.fileType,
          // On iOS and macOS, read-write permission (instead of add-only even when supported) is required to save to an album.
          albumName: albumName.isNotEmpty ? albumName : null,
        ),
      );
      scaffoldMessenger.showText(
        'The image has been saved to the gallery.',
        action: SnackBarAction(
            label: 'Open Gallery', onPressed: quillNativeBridge.openGalleryApp),
      );
      break;
    case QuillNativeBridgeFeature.saveImage:
      if (isFeatureUnsupported) {
        scaffoldMessenger.showText(
          kIsWeb
              ? 'Saving an image is not supported on web.'
              : 'Saving an image is not supported on ${defaultTargetPlatform.name}.',
        );
        return;
      }

      if (!context.mounted) return;
      final selectedImage = await showSelectImageDialog(context: context);

      if (selectedImage == null) {
        scaffoldMessenger.showText('Image save was canceled.');
        return;
      }
      final imageBytes = await loadAssetFile(selectedImage.assetPath);

      final imagePath = (await quillNativeBridge.saveImage(
        imageBytes,
        options: ImageSaveOptions(
          name: selectedImage.description,
          fileExtension: selectedImage.fileType,
        ),
      ))
          .filePath;
      if (!kIsWeb && imagePath == null) {
        scaffoldMessenger.showText('Image save was canceled.');
        return;
      }
      scaffoldMessenger.showText('The image has been saved at: $imagePath.');
      break;
  }
}

extension ScaffoldMessengerX on ScaffoldMessengerState {
  void showText(String text, {SnackBarAction? action}) {
    clearSnackBars();
    showSnackBar(SnackBar(content: Text(text), action: action));
  }
}