Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<provider
android:name="com.superlist.super_native_extensions.DataProvider"
android:authorities="com.example.flutter_ai_toolkit_example.SuperClipboardDataProvider"
android:exported="true"
android:grantUriPermissions="true" >
</provider>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
Expand Down
3 changes: 3 additions & 0 deletions example/devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
18 changes: 18 additions & 0 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ PODS:
- PromisesObjC (~> 2.4)
- camera_avfoundation (0.0.1):
- Flutter
- device_info_plus (0.0.1):
- Flutter
- file_selector_ios (0.0.1):
- Flutter
- Firebase/Auth (12.4.0):
Expand Down Expand Up @@ -77,6 +79,8 @@ PODS:
- GTMSessionFetcher/Core (5.0.0)
- image_picker_ios (0.0.1):
- Flutter
- irondash_engine_context (0.0.1):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
Expand All @@ -87,20 +91,25 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- super_native_extensions (0.0.1):
- Flutter
- url_launcher_ios (0.0.1):
- Flutter

DEPENDENCIES:
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`)
- firebase_app_check (from `.symlinks/plugins/firebase_app_check/ios`)
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- Flutter (from `Flutter`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- record_ios (from `.symlinks/plugins/record_ios/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)

SPEC REPOS:
Expand All @@ -122,6 +131,8 @@ SPEC REPOS:
EXTERNAL SOURCES:
camera_avfoundation:
:path: ".symlinks/plugins/camera_avfoundation/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_selector_ios:
:path: ".symlinks/plugins/file_selector_ios/ios"
firebase_app_check:
Expand All @@ -134,18 +145,23 @@ EXTERNAL SOURCES:
:path: Flutter
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
irondash_engine_context:
:path: ".symlinks/plugins/irondash_engine_context/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
record_ios:
:path: ".symlinks/plugins/record_ios/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
super_native_extensions:
:path: ".symlinks/plugins/super_native_extensions/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"

SPEC CHECKSUMS:
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
camera_avfoundation: 281867ff09f1da66f031a184ecfbc6f2e625c9f5
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
file_selector_ios: 80c12e90ad3f2045ed6819d03742f1a4c5ec3f93
Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e
firebase_app_check: 53a9efd793edad49230d8d49b19cb8d47b8450ed
Expand All @@ -162,11 +178,13 @@ SPEC CHECKSUMS:
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
GTMSessionFetcher: 02d6e866e90bc236f48a703a041dfe43e6221a29
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba
record_ios: 840d21cce013c5a3b2168b74a54ebdb4136359e2
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
super_native_extensions: 4916b3c627a9c7fffdc48a23a9eca0b1ac228fa7
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa

PODFILE CHECKSUM: 7773a3d1e948b3cef227c6713241e4fcfe42cda9
Expand Down
6 changes: 6 additions & 0 deletions example/macos/Flutter/GeneratedPluginRegistrant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,28 @@
import FlutterMacOS
import Foundation

import device_info_plus
import file_selector_macos
import firebase_app_check
import firebase_auth
import firebase_core
import irondash_engine_context
import path_provider_foundation
import record_macos
import shared_preferences_foundation
import super_native_extensions
import url_launcher_macos

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin"))
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}
18 changes: 18 additions & 0 deletions example/macos/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ PODS:
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- device_info_plus (0.0.1):
- FlutterMacOS
- file_selector_macos (0.0.1):
- FlutterMacOS
- Firebase/AppCheck (12.4.0):
Expand Down Expand Up @@ -77,6 +79,8 @@ PODS:
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GTMSessionFetcher/Core (5.0.0)
- irondash_engine_context (0.0.1):
- FlutterMacOS
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
Expand All @@ -86,18 +90,23 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- super_native_extensions (0.0.1):
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS

DEPENDENCIES:
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- firebase_app_check (from `Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos`)
- firebase_auth (from `Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos`)
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- irondash_engine_context (from `Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/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`)

SPEC REPOS:
Expand All @@ -116,6 +125,8 @@ SPEC REPOS:
- PromisesObjC

EXTERNAL SOURCES:
device_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
file_selector_macos:
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
firebase_app_check:
Expand All @@ -126,17 +137,22 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos
FlutterMacOS:
:path: Flutter/ephemeral
irondash_engine_context:
:path: Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
record_macos:
:path: Flutter/ephemeral/.symlinks/plugins/record_macos/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
super_native_extensions:
:path: Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos

SPEC CHECKSUMS:
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215
file_selector_macos: 3e56eaea051180007b900eacb006686fd54da150
Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e
firebase_app_check: 87116ccdfe0f153231af37b0431e96b0d5a76b9c
Expand All @@ -152,10 +168,12 @@ SPEC CHECKSUMS:
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
GTMSessionFetcher: 02d6e866e90bc236f48a703a041dfe43e6221a29
irondash_engine_context: da62996ee25616d2f01bbeb85dc115d813359478
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
record_macos: 4440ca269ad3b870ebb1965297a365d558f0c520
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
super_native_extensions: 85efee3a7495b46b04befcfc86ed12069264ebf3
url_launcher_macos: 175a54c831f4375a6cf895875f716ee5af3888ce

PODFILE CHECKSUM: abc7d4662afc18f3dac224359a4bbdfd943487c9
Expand Down
172 changes: 172 additions & 0 deletions lib/src/helpers/paste_helper/paste_handler.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright 2025 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:typed_data';

import 'package:cross_file/cross_file.dart';
import 'package:flutter/widgets.dart'
show TextEditingController, debugPrint, debugPrintStack;
import 'package:mime/mime.dart';
import 'package:super_clipboard/super_clipboard.dart';

import '../../providers/interface/attachments.dart';

/// Handles paste operations, supporting both text and image pasting.
///
/// This function processes the clipboard contents and either:
/// - Extracts and handles image data if images are present in the clipboard
/// - Inserts plain text into the provided text controller if no images are found
///
/// On web, it delegates to [handlePasteWeb] for more comprehensive handling
/// of web-specific clipboard APIs.
///
/// Parameters:
/// - [controller]: The text editing controller to insert text into
/// - [onAttachments]: Callback that receives a list of attachments when images are pasted.
/// If null, image pasting will be skipped even if images are available.
///
/// Returns:
/// A [Future] that completes when the paste operation is finished
Future<void> handlePaste({
required TextEditingController controller,
required void Function(List<Attachment> attachments)? onAttachments,
required void Function({
required TextEditingController controller,
required String text,
})
insertText,
}) async {
try {
final clipboard = SystemClipboard.instance;
if (clipboard == null) return;
final reader = await clipboard.read();

if (onAttachments != null) {
final imageFormats = [
Formats.png,
Formats.jpeg,
Formats.bmp,
Formats.gif,
Formats.tiff,
Formats.webp,
];

final fileFormats = [
Formats.pdf,
Formats.doc,
Formats.docx,
Formats.xls,
Formats.xlsx,
Formats.ppt,
Formats.pptx,
Formats.epub,
];

if (reader.canProvide(Formats.fileUri)) {
await reader.readValue(Formats.fileUri).then((val) async {
if (val != null) {
if (val.isScheme('file')) {
final path = val.toFilePath();
final file = XFile(path);
final attachment = await FileAttachment.fromFile(file);
onAttachments([attachment]);
}
}
});
return;
}

for (final format in fileFormats) {
if (reader.canProvide(format)) {
reader.getFile(format, (file) async {
final stream = file.getStream();

await stream.toList().then((chunks) {
final attachmentBytes = Uint8List.fromList(
chunks.expand((e) => e).toList(),
);
final mimeType =
lookupMimeType('', headerBytes: attachmentBytes) ??
'application/octet-stream';
final attachment = FileAttachment.fileOrImage(
name:
'pasted_file_${DateTime.now().millisecondsSinceEpoch}.${_getExtensionFromMime(mimeType)}',
mimeType: mimeType,
bytes: attachmentBytes,
);
onAttachments([attachment]);
return;
});
});
return;
}
}

for (final format in imageFormats) {
if (reader.canProvide(format)) {
reader.getFile(format, (file) async {
final stream = file.getStream();

await stream.toList().then((chunks) {
final attachmentBytes = Uint8List.fromList(
chunks.expand((e) => e).toList(),
);
final mimeType =
lookupMimeType('', headerBytes: attachmentBytes) ??
'image/png';
final attachment = ImageFileAttachment(
name:
'pasted_image_${DateTime.now().millisecondsSinceEpoch}.${_getExtensionFromMime(mimeType)}',
mimeType: mimeType,
bytes: attachmentBytes,
);
onAttachments([attachment]);
return;
});
});
return;
}
}
}

if (reader.canProvide(Formats.plainText)) {
final text = await reader.readValue(Formats.plainText);
if (text != null && text.isNotEmpty) {
insertText(controller: controller, text: text);
return;
}
}

if (reader.canProvide(Formats.htmlText)) {
final html = await reader.readValue(Formats.htmlText);
if (html != null && html.isNotEmpty) {
insertText(controller: controller, text: html);
return;
}
}
} catch (e, s) {
debugPrint('Error pasting image: $e');
debugPrintStack(stackTrace: s);
}
}

/// Determines the appropriate file extension for a given MIME type.
///
/// Parameters:
/// - [mimeType]: The MIME type to get the extension for (e.g., 'image/png')
///
/// Returns:
/// A string representing the file extension (without the dot), defaults to 'bin' if unknown
String _getExtensionFromMime(String mimeType, [List<int>? bytes]) {
String detectedMimeType = mimeType;
if (bytes != null &&
(mimeType.isEmpty || mimeType == 'application/octet-stream')) {
detectedMimeType = lookupMimeType('', headerBytes: bytes) ?? mimeType;
}
final extension = extensionFromMime(detectedMimeType);
if (extension == null || extension.isEmpty) {
return detectedMimeType.startsWith('image/') ? 'png' : 'bin';
}
return extension.startsWith('.') ? extension.substring(1) : extension;
}
Loading