From aea906f801ef98c2590e33c7539fa7dbd8842aa4 Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Fri, 20 Mar 2026 14:38:41 +0300 Subject: [PATCH] feat: Paste image from clipboard on desktop/web --- lib/l10n/intl_en.arb | 5 +++++ lib/l10n/intl_ru.arb | 7 ++++++- lib/pages/chat/chat.dart | 10 ++++++++++ lib/pages/chat/chat_input_row.dart | 15 +++++++++++++++ linux/flutter/generated_plugin_registrant.cc | 4 ++++ linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 ++ pubspec.lock | 8 ++++++++ pubspec.yaml | 1 + windows/flutter/generated_plugin_registrant.cc | 3 +++ windows/flutter/generated_plugins.cmake | 1 + 11 files changed, 56 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 1ce086ee..cf52814c 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1618,6 +1618,11 @@ "type": "String", "placeholders": {} }, + "pasteImage": "Image from clipboard", + "@pasteImage": { + "type": "String", + "placeholders": {} + }, "sendImages": "Send {count} image", "@sendImages": { "type": "String", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 7341066d..cd2661cb 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1452,6 +1452,11 @@ "type": "String", "placeholders": {} }, + "pasteImage": "Изображение из буфера обмена", + "@pasteImage": { + "type": "String", + "placeholders": {} + }, "sendMessages": "Отправить сообщения", "@sendMessages": { "type": "String", @@ -2695,4 +2700,4 @@ } } } -} \ No newline at end of file +} diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 6432bc39..b4f04a2a 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -14,6 +14,7 @@ import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; import 'package:matrix/matrix.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; +import 'package:pasteboard/pasteboard.dart'; import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/config/themes.dart'; @@ -643,6 +644,11 @@ class ChatController extends State ); } + Future sendImageFromClipBoardAction() async { + final image = await Pasteboard.image; + await sendImageFromClipBoard(image); + } + Future sendImageFromClipBoard(Uint8List? image) async { if (image == null) return; await showAdaptiveDialog( @@ -1219,6 +1225,9 @@ class ChatController extends State case AddPopupMenuActions.location: sendLocationAction(); return; + case AddPopupMenuActions.pasteImage: + sendImageFromClipBoardAction(); + return; } } @@ -1421,4 +1430,5 @@ enum AddPopupMenuActions { photoCamera, videoCamera, location, + pasteImage, } diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index ae774ca7..fd45af68 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -172,6 +172,21 @@ class ChatInputRow extends StatelessWidget { contentPadding: const EdgeInsets.all(0), ), ), + if (!PlatformInfos.isMobile) + PopupMenuItem( + value: AddPopupMenuActions.pasteImage, + child: ListTile( + leading: CircleAvatar( + backgroundColor: + theme.colorScheme.onPrimaryContainer, + foregroundColor: + theme.colorScheme.primaryContainer, + child: const Icon(Icons.content_paste), + ), + title: Text(L10n.of(context).pasteImage), + contentPadding: const EdgeInsets.all(0), + ), + ), PopupMenuItem( value: AddPopupMenuActions.image, child: ListTile( diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index d18eb4a9..c33521ea 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) media_kit_video_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitVideoPlugin"); media_kit_video_plugin_register_with_registrar(media_kit_video_registrar); + g_autoptr(FlPluginRegistrar) pasteboard_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin"); + pasteboard_plugin_register_with_registrar(pasteboard_registrar); g_autoptr(FlPluginRegistrar) record_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin"); record_linux_plugin_register_with_registrar(record_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index c79bbc48..ae0fc6c4 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -13,6 +13,7 @@ list(APPEND FLUTTER_PLUGIN_LIST handy_window media_kit_libs_linux media_kit_video + pasteboard record_linux screen_retriever_linux sqlcipher_flutter_libs diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 162d6d46..c6f90391 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -22,6 +22,7 @@ import geolocator_apple import just_audio import media_kit_video import package_info_plus +import pasteboard import record_macos import screen_retriever_macos import share_plus @@ -53,6 +54,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin")) RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 2bc5394a..4ad9a860 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1229,6 +1229,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.3" + pasteboard: + dependency: "direct main" + description: + name: pasteboard + sha256: fedbe8da188d2f713aa8b01260737342e6e1087534a3ab26e1a719f8d3e8f32f + url: "https://pub.dev" + source: hosted + version: "0.5.0" path: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index b713c793..c47645bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,6 +60,7 @@ dependencies: opus_caf_converter_dart: ^1.0.1 package_info_plus: ^9.0.0 particles_network: ^1.9.3 + pasteboard: ^0.5.0 path: ^1.9.0 path_provider: ^2.1.2 pretty_qr_code: ^3.6.0 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index f445982a..7928d6d1 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); MediaKitVideoPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); + PasteboardPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PasteboardPlugin")); RecordWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("RecordWindowsPluginCApi")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 7ff6d1d1..46d7ece4 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -13,6 +13,7 @@ list(APPEND FLUTTER_PLUGIN_LIST geolocator_windows media_kit_libs_windows_video media_kit_video + pasteboard record_windows screen_retriever_windows share_plus