parent
27137c488d
commit
36f61a62d8
12 changed files with 154 additions and 15 deletions
|
|
@ -51,6 +51,16 @@
|
|||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
<!-- Needed by pasteboard, for pasting files from the clipboard -->
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
|
|
|
|||
6
android/app/src/main/res/xml/provider_paths.xml
Normal file
6
android/app/src/main/res/xml/provider_paths.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path
|
||||
name="external_files"
|
||||
path="." />
|
||||
</paths>
|
||||
|
|
@ -18,6 +18,10 @@ import "package:nexus/controllers/power_level.dart";
|
|||
import "package:nexus/models/content/message.dart";
|
||||
import "package:nexus/models/relation_type.dart";
|
||||
import "package:nexus/widgets/composer/overlays/tagger_overlay.dart";
|
||||
import "package:nexus/widgets/composer/paste_files_action.dart";
|
||||
import "package:pasteboard/pasteboard.dart";
|
||||
import "package:path/path.dart";
|
||||
import "package:path_provider/path_provider.dart";
|
||||
import "package:nexus/widgets/composer/relation_preview.dart";
|
||||
import "package:nexus/main.dart";
|
||||
|
||||
|
|
@ -100,6 +104,46 @@ class const Composer(
|
|||
}
|
||||
}
|
||||
|
||||
/// Attaches [bytes] as a file called [filename], e.g. for pasted images.
|
||||
Future<void> attachBytes(Uint8List bytes, String filename) async {
|
||||
// A new directory each time, so files with the same name don't clash
|
||||
final directory = await (await getTemporaryDirectory()).createTemp(
|
||||
"attachment",
|
||||
);
|
||||
final file = await File(join(directory.path, filename))
|
||||
.writeAsBytes(bytes);
|
||||
|
||||
await ref
|
||||
.read(AttachmentController.provider(roomId).notifier)
|
||||
.add(.new(file.path));
|
||||
}
|
||||
|
||||
/// Attaches the first file on the clipboard, or the image on it if there
|
||||
/// are no files. Returns whether there was either, so that text can be
|
||||
/// pasted otherwise.
|
||||
Future<bool> pasteFiles() async {
|
||||
// Only one attachment is supported at a time
|
||||
if (attachment != null) return false;
|
||||
|
||||
// Failing to read the clipboard counts as it being empty, so that text
|
||||
// can still be pasted
|
||||
final files = await Pasteboard.files().onError((_, _) => const []);
|
||||
if (files.firstOrNull case final path?) {
|
||||
ref
|
||||
.read(AttachmentController.provider(roomId).notifier)
|
||||
.add(XFile(path))
|
||||
.onError(showError);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (await Pasteboard.image.onError((_, _) => null) case final image?) {
|
||||
attachBytes(image, "image.png").onError(showError);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void send() {
|
||||
if (controller.value.text.isEmpty && attachment == null ||
|
||||
attachment != null && attachment.$2 == null) {
|
||||
|
|
@ -257,6 +301,13 @@ class const Composer(
|
|||
|
||||
return KeyEventResult.ignored;
|
||||
},
|
||||
// Overrides the keyboard shortcut for pasting
|
||||
child: Actions(
|
||||
actions: {
|
||||
PasteTextIntent: PasteFilesAction(
|
||||
pasteFiles,
|
||||
),
|
||||
},
|
||||
child: TextField(
|
||||
maxLines: 12,
|
||||
minLines: 1,
|
||||
|
|
@ -272,6 +323,43 @@ class const Composer(
|
|||
controller: controller,
|
||||
key: key,
|
||||
focusNode: node,
|
||||
// The toolbar's paste button doesn't use the shortcut's action, so replace it too
|
||||
contextMenuBuilder: (_, editableText) =>
|
||||
AdaptiveTextSelectionToolbar.buttonItems(
|
||||
anchors:
|
||||
editableText.contextMenuAnchors,
|
||||
buttonItems: editableText
|
||||
.contextMenuButtonItems
|
||||
.map(
|
||||
(item) => item.type == .paste
|
||||
? item.copyWith(
|
||||
onPressed: () async {
|
||||
editableText
|
||||
.hideToolbar();
|
||||
if (!await pasteFiles()) {
|
||||
item.onPressed
|
||||
?.call();
|
||||
}
|
||||
},
|
||||
)
|
||||
: item,
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
// Images pasted from Android keyboards' clipboards are inserted as content
|
||||
contentInsertionConfiguration: .new(
|
||||
onContentInserted: (content) {
|
||||
if (attachment != null) return;
|
||||
|
||||
if (content.data case final data?) {
|
||||
attachBytes(
|
||||
data,
|
||||
"image.${content.mimeType.split("/").last}",
|
||||
).onError(showError);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
12
lib/widgets/composer/paste_files_action.dart
Normal file
12
lib/widgets/composer/paste_files_action.dart
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import "package:material_ui/material_ui.dart";
|
||||
|
||||
/// Overrides a text field's paste, to paste files and images with
|
||||
/// [pasteFiles], falling back to pasting text if it returns false.
|
||||
class PasteFilesAction(final Future<bool> Function() pasteFiles)
|
||||
extends Action<PasteTextIntent> {
|
||||
@override
|
||||
Future<void> invoke(PasteTextIntent intent) async {
|
||||
// callingAction is the text field's own paste action, which this overrides
|
||||
if (!await pasteFiles()) callingAction?.invoke(intent);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||
#include <media_kit_video/media_kit_video_plugin.h>
|
||||
#include <pasteboard/pasteboard_plugin.h>
|
||||
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
#include <webcrypto/webcrypto_plugin.h>
|
||||
|
|
@ -32,6 +33,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) screen_retriever_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin");
|
||||
screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
file_selector_linux
|
||||
media_kit_libs_linux
|
||||
media_kit_video
|
||||
pasteboard
|
||||
screen_retriever_linux
|
||||
url_launcher_linux
|
||||
webcrypto
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ let
|
|||
"36.0.0"
|
||||
];
|
||||
cmakeVersions = [ "3.22.1" ];
|
||||
platformVersions = [ "36" ];
|
||||
platformVersions = [
|
||||
"34"
|
||||
"36"
|
||||
];
|
||||
abiVersions = [
|
||||
"armeabi-v7a"
|
||||
"arm64-v8a"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import flutter_local_notifications
|
|||
import media_kit_libs_macos_video
|
||||
import media_kit_video
|
||||
import package_info_plus
|
||||
import pasteboard
|
||||
import screen_retriever_macos
|
||||
import shared_preferences_foundation
|
||||
import url_launcher_macos
|
||||
|
|
@ -27,6 +28,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
||||
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
|
||||
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
|
|
|
|||
|
|
@ -1047,6 +1047,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
pasteboard:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pasteboard
|
||||
sha256: fedbe8da188d2f713aa8b01260737342e6e1087534a3ab26e1a719f8d3e8f32f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ dependencies:
|
|||
git:
|
||||
url: https://github.com/Henry-Hiles/xdg_desktop_portal.dart
|
||||
ref: quad/nexus
|
||||
pasteboard: 0.5.0
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: 2.16.2
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
|
||||
#include <media_kit_video/media_kit_video_plugin_c_api.h>
|
||||
#include <pasteboard/pasteboard_plugin.h>
|
||||
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
#include <webcrypto/webcrypto_plugin.h>
|
||||
|
|
@ -27,6 +28,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||
registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi"));
|
||||
MediaKitVideoPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi"));
|
||||
PasteboardPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PasteboardPlugin"));
|
||||
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
file_selector_windows
|
||||
media_kit_libs_windows_video
|
||||
media_kit_video
|
||||
pasteboard
|
||||
screen_retriever_windows
|
||||
url_launcher_windows
|
||||
webcrypto
|
||||
|
|
|
|||
Loading…
Reference in a new issue