✨ Paste to upload
This commit is contained in:
@ -8,6 +8,10 @@ extension SolianExtenions on BuildContext {
|
||||
));
|
||||
}
|
||||
|
||||
void clearSnackbar() {
|
||||
ScaffoldMessenger.of(this).clearSnackBars();
|
||||
}
|
||||
|
||||
Future<void> showErrorDialog(dynamic exception) {
|
||||
return showDialog<void>(
|
||||
useRootNavigator: true,
|
||||
|
@ -99,12 +99,16 @@ class SolianMessages extends Translations {
|
||||
'attachmentAddGalleryVideo': 'Gallery video',
|
||||
'attachmentAddCameraPhoto': 'Capture photo',
|
||||
'attachmentAddCameraVideo': 'Capture video',
|
||||
'attachmentAddClipboard': 'Paste file',
|
||||
'attachmentAddFile': 'Attach file',
|
||||
'attachmentSetting': 'Adjust attachment',
|
||||
'attachmentAlt': 'Alternative text',
|
||||
'attachmentLoadFailed': 'Load Attachment Failed',
|
||||
'attachmentLoadFailedCaption':
|
||||
'Something went wrong during loading the attachment metadata...',
|
||||
'attachmentUploading': 'Uploading @name...',
|
||||
'attachmentUploadingWebMode':
|
||||
'Uploading @name... Due to browser\'s limitation, calculate attachment information may cause some lag...',
|
||||
'realm': 'Realm',
|
||||
'realms': 'Realms',
|
||||
'realmOrganizing': 'Organize a realm',
|
||||
@ -325,11 +329,15 @@ class SolianMessages extends Translations {
|
||||
'attachmentAddGalleryVideo': '相册视频',
|
||||
'attachmentAddCameraPhoto': '拍摄图片',
|
||||
'attachmentAddCameraVideo': '拍摄视频',
|
||||
'attachmentAddClipboard': '粘贴文件',
|
||||
'attachmentAddFile': '附加文件',
|
||||
'attachmentSetting': '调整附件',
|
||||
'attachmentAlt': '替代文字',
|
||||
'attachmentLoadFailed': '加载失败',
|
||||
'attachmentLoadFailedCaption': '有错误发生于加载附件元数据的过程中了…',
|
||||
'attachmentUploading': '上传附件 @name 中…',
|
||||
'attachmentUploadingWebMode':
|
||||
'上传附件 @name 中… 由于浏览器单线程限制,计算所需资源可能会导致界面卡顿…',
|
||||
'realm': '领域',
|
||||
'realms': '领域',
|
||||
'realmOrganizing': '组织领域',
|
||||
|
@ -1,19 +1,20 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:path/path.dart' show basename;
|
||||
import 'package:solian/exts.dart';
|
||||
import 'package:solian/models/attachment.dart';
|
||||
import 'package:solian/platform.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/providers/content/attachment.dart';
|
||||
import 'package:super_clipboard/super_clipboard.dart';
|
||||
import 'package:super_drag_and_drop/super_drag_and_drop.dart';
|
||||
|
||||
class AttachmentPublishPopup extends StatefulWidget {
|
||||
@ -153,10 +154,48 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
|
||||
void pasteFileToUpload() async {
|
||||
final clipboard = SystemClipboard.instance;
|
||||
if (clipboard == null) return;
|
||||
final reader = await clipboard.read();
|
||||
for (final format in Formats.standardFormats.whereType<FileFormat>()) {
|
||||
if (reader.canProvide(format)) {
|
||||
reader.getFile(format, (file) async {
|
||||
final data = await file.readAll();
|
||||
await uploadAttachment(
|
||||
data,
|
||||
file.fileName ?? 'unknown',
|
||||
await calculateBytesSha256(data),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handlePasteFile(ClipboardReadEvent event) async {
|
||||
final reader = await event.getClipboardReader();
|
||||
for (final format in Formats.standardFormats.whereType<FileFormat>()) {
|
||||
if (reader.canProvide(format)) {
|
||||
reader.getFile(format, (file) async {
|
||||
final data = await file.readAll();
|
||||
await uploadAttachment(
|
||||
data,
|
||||
file.fileName ?? 'unknown',
|
||||
await calculateBytesSha256(data),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> uploadAttachment(Uint8List data, String path, String hash,
|
||||
{double? ratio}) async {
|
||||
final AttachmentProvider provider = Get.find();
|
||||
try {
|
||||
context.showSnackbar((PlatformInfo.isWeb
|
||||
? 'attachmentUploadingWebMode'
|
||||
: 'attachmentUploading')
|
||||
.trParams({'name': basename(path)}));
|
||||
final resp = await provider.createAttachment(
|
||||
data,
|
||||
path,
|
||||
@ -167,6 +206,7 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
||||
var result = Attachment.fromJson(resp.body);
|
||||
setState(() => _attachments.add(result));
|
||||
widget.onUpdate(_attachments.map((e) => e!.id).toList());
|
||||
context.clearSnackbar();
|
||||
} catch (err) {
|
||||
rethrow;
|
||||
}
|
||||
@ -222,11 +262,13 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
revertMetadataList();
|
||||
ClipboardEvents.instance?.registerPasteEventListener(handlePasteFile);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
ClipboardEvents.instance?.unregisterPasteEventListener(handlePasteFile);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -256,11 +298,9 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
||||
final data = await file.readAll();
|
||||
await uploadAttachment(
|
||||
data,
|
||||
file.fileName ?? 'attachment',
|
||||
file.fileName ?? 'unknown',
|
||||
await calculateBytesSha256(data),
|
||||
);
|
||||
}, onError: (error) {
|
||||
print('Error reading value $error');
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -359,6 +399,13 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
||||
alignment: WrapAlignment.center,
|
||||
runAlignment: WrapAlignment.center,
|
||||
children: [
|
||||
if (PlatformInfo.isDesktop)
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.paste),
|
||||
label: Text('attachmentAddClipboard'.tr),
|
||||
style: const ButtonStyle(visualDensity: density),
|
||||
onPressed: () => pasteFileToUpload(),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.add_photo_alternate),
|
||||
label: Text('attachmentAddGalleryPhoto'.tr),
|
||||
|
@ -134,15 +134,17 @@ class _PostItemState extends State<PostItem> {
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.75),
|
||||
),
|
||||
Text(
|
||||
'postRepliedNotify'.trParams(
|
||||
{'username': '@${widget.item.replyTo!.author.name}'},
|
||||
),
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.75),
|
||||
),
|
||||
).paddingOnly(left: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'postRepliedNotify'.trParams(
|
||||
{'username': '@${widget.item.replyTo!.author.name}'},
|
||||
),
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.75),
|
||||
),
|
||||
).paddingOnly(left: 6),
|
||||
),
|
||||
],
|
||||
).paddingOnly(left: 12),
|
||||
Card(
|
||||
@ -167,15 +169,17 @@ class _PostItemState extends State<PostItem> {
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.75),
|
||||
),
|
||||
Text(
|
||||
'postRepostedNotify'.trParams(
|
||||
{'username': '@${widget.item.repostTo!.author.name}'},
|
||||
),
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.75),
|
||||
),
|
||||
).paddingOnly(left: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'postRepostedNotify'.trParams(
|
||||
{'username': '@${widget.item.repostTo!.author.name}'},
|
||||
),
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.75),
|
||||
),
|
||||
).paddingOnly(left: 6),
|
||||
),
|
||||
],
|
||||
).paddingOnly(left: 12),
|
||||
Card(
|
||||
|
Reference in New Issue
Block a user