From 3818328afe7d26a957fb79c0e21313bbfb7b5275 Mon Sep 17 00:00:00 2001
From: LittleSheep <littlesheep.code@hotmail.com>
Date: Thu, 6 Feb 2025 15:04:04 +0800
Subject: [PATCH] :sparkles: Cmd/Ctrl-V to paste image

---
 lib/screens/post/post_editor.dart        | 32 ++++++++++++++++++++++++
 lib/widgets/chat/chat_message_input.dart | 31 ++++++++++++++++++++++-
 2 files changed, 62 insertions(+), 1 deletion(-)

diff --git a/lib/screens/post/post_editor.dart b/lib/screens/post/post_editor.dart
index 8794750..10ba720 100644
--- a/lib/screens/post/post_editor.dart
+++ b/lib/screens/post/post_editor.dart
@@ -1,11 +1,17 @@
+import 'dart:io';
+
 import 'package:collection/collection.dart';
 import 'package:dropdown_button2/dropdown_button2.dart';
 import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:gap/gap.dart';
 import 'package:go_router/go_router.dart';
+import 'package:hotkey_manager/hotkey_manager.dart';
 import 'package:material_symbols_icons/symbols.dart';
+import 'package:pasteboard/pasteboard.dart';
 import 'package:styled_widget/styled_widget.dart';
 import 'package:surface/controllers/post_write_controller.dart';
 import 'package:surface/providers/config.dart';
@@ -20,6 +26,8 @@ import 'package:surface/widgets/post/post_meta_editor.dart';
 import 'package:surface/widgets/dialog.dart';
 import 'package:provider/provider.dart';
 
+import '../../types/attachment.dart';
+
 class PostEditorExtra {
   final String? text;
   final String? title;
@@ -94,15 +102,39 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
     );
   }
 
+  final HotKey _pasteHotKey = HotKey(
+    key: PhysicalKeyboardKey.keyV,
+    modifiers: [Platform.isMacOS ? HotKeyModifier.meta : HotKeyModifier.control],
+    scope: HotKeyScope.inapp,
+  );
+
+  void _registerHotKey() {
+    if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
+    hotKeyManager.register(_pasteHotKey, keyDownHandler: (_) async {
+      final imageBytes = await Pasteboard.image;
+      if (imageBytes == null) return;
+      _writeController.addAttachments([
+        PostWriteMedia.fromBytes(
+          imageBytes,
+          'attachmentPastedImage'.tr(),
+          SnMediaType.image,
+        ),
+      ]);
+      setState(() {});
+    });
+  }
+
   @override
   void dispose() {
     _writeController.dispose();
+    if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) hotKeyManager.unregister(_pasteHotKey);
     super.dispose();
   }
 
   @override
   void initState() {
     super.initState();
+    _registerHotKey();
     if (!PostWriteController.kTitleMap.keys.contains(widget.mode)) {
       context.showErrorDialog('Unknown post type');
       Navigator.pop(context);
diff --git a/lib/widgets/chat/chat_message_input.dart b/lib/widgets/chat/chat_message_input.dart
index 8da6692..d03932f 100644
--- a/lib/widgets/chat/chat_message_input.dart
+++ b/lib/widgets/chat/chat_message_input.dart
@@ -1,10 +1,15 @@
+import 'dart:io';
 import 'dart:math' show min;
 
 import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:gap/gap.dart';
 import 'package:google_fonts/google_fonts.dart';
+import 'package:hotkey_manager/hotkey_manager.dart';
 import 'package:material_symbols_icons/symbols.dart';
+import 'package:pasteboard/pasteboard.dart';
 import 'package:provider/provider.dart';
 import 'package:styled_widget/styled_widget.dart';
 import 'package:surface/controllers/chat_message_controller.dart';
@@ -38,9 +43,30 @@ class ChatMessageInputState extends State<ChatMessageInput> {
   final TextEditingController _contentController = TextEditingController();
   final FocusNode _focusNode = FocusNode();
 
+  final HotKey _pasteHotKey = HotKey(
+    key: PhysicalKeyboardKey.keyV,
+    modifiers: [Platform.isMacOS ? HotKeyModifier.meta : HotKeyModifier.control],
+    scope: HotKeyScope.inapp,
+  );
+
+  void _registerHotKey() {
+    if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
+    hotKeyManager.register(_pasteHotKey, keyDownHandler: (_) async {
+      final imageBytes = await Pasteboard.image;
+      if (imageBytes == null) return;
+      _attachments.add(PostWriteMedia.fromBytes(
+        imageBytes,
+        'attachmentPastedImage'.tr(),
+        SnMediaType.image,
+      ));
+      setState(() {});
+    });
+  }
+
   @override
   void initState() {
     super.initState();
+    _registerHotKey();
     _contentController.addListener(() {
       if (_contentController.text.isNotEmpty) {
         widget.controller.pingTypingStatus();
@@ -177,6 +203,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
   void dispose() {
     _contentController.dispose();
     _focusNode.dispose();
+    if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) hotKeyManager.unregister(_pasteHotKey);
     super.dispose();
   }
 
@@ -420,7 +447,9 @@ class _StickerPicker extends StatelessWidget {
                             child: Tooltip(
                               richMessage: TextSpan(
                                 children: [
-                                  TextSpan(text: ':${element.pack.prefix}${element.alias}:\n', style: GoogleFonts.robotoMono()),
+                                  TextSpan(
+                                      text: ':${element.pack.prefix}${element.alias}:\n',
+                                      style: GoogleFonts.robotoMono()),
                                   TextSpan(text: element.name).bold(),
                                 ],
                               ),