diff --git a/android/app/build.gradle b/android/app/build.gradle
index 23419542c7..a985cab9c1 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -33,6 +33,7 @@ if (keystorePropertiesFile.exists()) {
android {
compileSdkVersion 33
+ ndkVersion flutter.ndkVersion
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -44,7 +45,7 @@ android {
defaultConfig {
applicationId "com.twake.twake"
- minSdkVersion 21
+ minSdkVersion 23
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 78d5c7eb27..700a8dbbcd 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -40,7 +40,7 @@
android:requestLegacyExternalStorage="true"
android:allowBackup="false"
android:fullBackupContent="false"
- >
+ >
+
+
diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb
index 0be0f07118..d8bc462966 100644
--- a/assets/l10n/intl_en.arb
+++ b/assets/l10n/intl_en.arb
@@ -2760,5 +2760,11 @@
"thisMessageHasBeenEncrypted": "This message has been encrypted",
"roomCreationFailed": "Room creation failed",
"errorGettingPdf": "Error getting PDF",
- "errorPreviewingFile": "Error previewing file"
+ "errorPreviewingFile": "Error previewing file",
+ "paste": "Paste",
+ "cut": "Cut",
+ "pasteImageFailed": "Paste image failed",
+ "copyImageFailed": "Copy image failed",
+ "fileFormatNotSupported": "File format not supported",
+ "copyImageSuccess": "Image copied to clipboard"
}
\ No newline at end of file
diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart
index bbace50d3a..f35800111c 100644
--- a/lib/pages/chat/chat.dart
+++ b/lib/pages/chat/chat.dart
@@ -31,6 +31,7 @@ import 'package:fluffychat/presentation/mixins/media_picker_mixin.dart';
import 'package:fluffychat/presentation/mixins/send_files_mixin.dart';
import 'package:fluffychat/presentation/model/forward/forward_argument.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
+import 'package:fluffychat/utils/clipboard.dart';
import 'package:fluffychat/utils/extension/build_context_extension.dart';
import 'package:fluffychat/utils/extension/value_notifier_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
@@ -47,7 +48,6 @@ import 'package:fluffychat/widgets/mixins/popup_context_menu_action_mixin.dart';
import 'package:fluffychat/widgets/mixins/popup_menu_widget_mixin.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
-import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
@@ -776,8 +776,41 @@ class ChatController extends State
return copyString;
}
+ void copySingleEventAction() async {
+ if (selectedEvents.length == 1) {
+ final event = selectedEvents.first;
+ if (event.messageType == MessageTypes.Image && PlatformInfos.isWeb) {
+ final matrixFile = event.getMatrixFile() ??
+ await event.downloadAndDecryptAttachment(
+ getThumbnail: true,
+ );
+ try {
+ if (matrixFile.filePath != null) {
+ await Clipboard.instance.copyImageAsStream(
+ File(matrixFile.filePath!),
+ mimeType: event.mimeType,
+ );
+ } else if (matrixFile.bytes != null) {
+ await Clipboard.instance.copyImageAsBytes(
+ matrixFile.bytes!,
+ mimeType: event.mimeType,
+ );
+ }
+ } catch (e) {
+ TwakeSnackBar.show(context, L10n.of(context)!.copyImageFailed);
+ Logs().e(
+ 'copySingleEventAction(): failed to copy file ${matrixFile.name}',
+ );
+ }
+ } else {
+ copyEventsAction();
+ }
+ }
+ }
+
void copyEventsAction() {
- Clipboard.setData(ClipboardData(text: _getSelectedEventString()));
+ Clipboard.instance.copyText(_getSelectedEventString());
+
showEmojiPickerNotifier.value = false;
setState(() {
selectedEvents.clear();
@@ -1467,7 +1500,7 @@ class ChatController extends State
break;
case ChatContextMenuActions.copyMessage:
onSelectMessage(event);
- copyEventsAction();
+ copySingleEventAction();
break;
case ChatContextMenuActions.pinMessage:
onSelectMessage(event);
diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart
index 24a9b50775..f45e3a29e1 100644
--- a/lib/pages/chat/chat_view.dart
+++ b/lib/pages/chat/chat_view.dart
@@ -38,7 +38,7 @@ class ChatView extends StatelessWidget {
icon: Icons.copy_outlined,
tooltip: L10n.of(context)!.copy,
onTap: () => controller
- .actionWithClearSelections(controller.copyEventsAction),
+ .actionWithClearSelections(controller.copySingleEventAction),
),
if (controller.canRedactSelectedEvents)
TwakeIconButton(
diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart
index bce81fe17c..aee2996bc6 100644
--- a/lib/pages/chat/input_bar.dart
+++ b/lib/pages/chat/input_bar.dart
@@ -1,7 +1,13 @@
+import 'package:fluffychat/presentation/enum/chat/popup_menu_item_web_enum.dart';
+import 'package:fluffychat/presentation/extensions/text_editting_controller_extension.dart';
+import 'package:fluffychat/presentation/mixins/paste_image_mixin.dart';
+import 'package:fluffychat/utils/clipboard.dart';
import 'package:fluffychat/utils/extension/raw_key_event_extension.dart';
import 'package:fluffychat/widgets/avatar/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
+import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart' as flutter;
import 'package:emojis/emoji.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
@@ -15,7 +21,7 @@ import 'package:fluffychat/widgets/mxc_image.dart';
import 'command_hints.dart';
-class InputBar extends StatelessWidget {
+class InputBar extends StatelessWidget with PasteImageMixin {
final Room? room;
final int? minLines;
final int? maxLines;
@@ -311,52 +317,176 @@ class InputBar extends StatelessWidget {
onSubmitted?.call(controller?.text ?? '');
}
},
- child: TypeAheadField