Drag to upload

This commit is contained in:
LittleSheep 2024-06-29 20:25:29 +08:00
parent 85bba21285
commit e336d2372a
15 changed files with 304 additions and 160 deletions

View File

@ -30,6 +30,13 @@
</intent-filter>
</receiver>
<provider
android:name="com.superlist.super_native_extensions.DataProvider"
android:authorities="dev.solsynth.solian.SuperClipboardDataProvider"
android:exported="true"
android:grantUriPermissions="true" >
</provider>
<activity
android:name=".MainActivity"
android:exported="true"

View File

@ -33,7 +33,9 @@ void main() async {
appRunner: () async {
WidgetsFlutterBinding.ensureInitialized();
if (!PlatformInfo.isWeb) {
await protocolHandler.register('solink');
}
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,

View File

@ -179,7 +179,7 @@ class AccountProvider extends GetxController {
Future<void> registerPushNotifications() async {
final AuthProvider auth = Get.find();
if (!await auth.isAuthorized) throw Exception('unauthorized');
if (!await auth.isAuthorized) return;
late final String? token;
late final String provider;

View File

@ -1,21 +1,40 @@
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:get/get.dart';
import 'package:path/path.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/services.dart';
import 'package:image/image.dart' as img;
Future<String> calculateFileSha256(File file) async {
final bytes = await Isolate.run(() => file.readAsBytesSync());
final digest = await Isolate.run(() => sha256.convert(bytes));
Future<String> calculateBytesSha256(Uint8List data) async {
Digest digest;
if (PlatformInfo.isWeb) {
digest = sha256.convert(data);
} else {
digest = await Isolate.run(() => sha256.convert(data));
}
return digest.toString();
}
Future<String> calculateFileSha256(File file) async {
Uint8List bytes;
if (PlatformInfo.isWeb) {
bytes = await file.readAsBytes();
} else {
bytes = await Isolate.run(() => file.readAsBytesSync());
}
return await calculateBytesSha256(bytes);
}
Future<double> calculateFileAspectRatio(File file) async {
if (PlatformInfo.isWeb) {
return 1;
}
final bytes = await Isolate.run(() => file.readAsBytesSync());
final decoder = await Isolate.run(() => img.findDecoderForData(bytes));
if (decoder == null) return 1;
@ -25,7 +44,10 @@ Future<double> calculateFileAspectRatio(File file) async {
}
class AttachmentProvider extends GetConnect {
static Map<String, String> mimetypeOverrides = {'mov': 'video/quicktime'};
static Map<String, String> mimetypeOverrides = {
'mov': 'video/quicktime',
'mp4': 'video/mp4'
};
@override
void onInit() {
@ -45,7 +67,8 @@ class AttachmentProvider extends GetConnect {
return resp;
}
Future<Response> createAttachment(File file, String hash, String usage,
Future<Response> createAttachment(
Uint8List data, String path, String hash, String usage,
{double? ratio}) async {
final AuthProvider auth = Get.find();
if (!await auth.isAuthorized) throw Exception('unauthorized');
@ -55,13 +78,12 @@ class AttachmentProvider extends GetConnect {
timeout: const Duration(minutes: 3),
);
final filePayload =
MultipartFile(await file.readAsBytes(), filename: basename(file.path));
final fileAlt = basename(file.path).contains('.')
? basename(file.path).substring(0, basename(file.path).lastIndexOf('.'))
: basename(file.path);
final fileExt = basename(file.path)
.substring(basename(file.path).lastIndexOf('.') + 1)
final filePayload = MultipartFile(data, filename: basename(path));
final fileAlt = basename(path).contains('.')
? basename(path).substring(0, basename(path).lastIndexOf('.'))
: basename(path);
final fileExt = basename(path)
.substring(basename(path).lastIndexOf('.') + 1)
.toLowerCase();
// Override for some files cannot be detected mimetype by server-side

View File

@ -88,7 +88,8 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
final file = File(image.path);
final hash = await calculateFileSha256(file);
attachResp = await provider.createAttachment(
file,
await file.readAsBytes(),
file.path,
hash,
'p.$position',
ratio: await calculateFileAspectRatio(file),

View File

@ -178,12 +178,6 @@ class _AttachmentListState extends State<AttachmentList> {
);
}
@override
void initState() {
super.initState();
getMetadataList();
}
@override
Widget build(BuildContext context) {
if (widget.attachmentsId.isEmpty) {
@ -236,7 +230,8 @@ class _AttachmentListState extends State<AttachmentList> {
const radius = BorderRadius.all(Radius.circular(16));
return Container(
decoration: BoxDecoration(
border: Border.all(color: Theme.of(context).dividerColor, width: 1),
border:
Border.all(color: Theme.of(context).dividerColor, width: 1),
borderRadius: radius,
),
child: ClipRRect(

View File

@ -1,6 +1,10 @@
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';
@ -10,6 +14,7 @@ import 'package:solian/exts.dart';
import 'package:solian/models/attachment.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/content/attachment.dart';
import 'package:super_drag_and_drop/super_drag_and_drop.dart';
class AttachmentPublishPopup extends StatefulWidget {
final String usage;
@ -24,8 +29,7 @@ class AttachmentPublishPopup extends StatefulWidget {
});
@override
State<AttachmentPublishPopup> createState() =>
_AttachmentPublishPopupState();
State<AttachmentPublishPopup> createState() => _AttachmentPublishPopupState();
}
class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
@ -51,7 +55,8 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
try {
await uploadAttachment(
file,
await file.readAsBytes(),
file.path,
hash,
ratio: await calculateFileAspectRatio(file),
);
@ -77,7 +82,8 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
const ratio = 16 / 9;
try {
await uploadAttachment(file, hash, ratio: ratio);
await uploadAttachment(await file.readAsBytes(), file.path, hash,
ratio: ratio);
} catch (err) {
context.showErrorDialog(err);
}
@ -100,7 +106,7 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
for (final file in files) {
final hash = await calculateFileSha256(file);
try {
await uploadAttachment(file, hash);
await uploadAttachment(await file.readAsBytes(), file.path, hash);
} catch (err) {
context.showErrorDialog(err);
}
@ -134,7 +140,12 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
}
try {
await uploadAttachment(file, hash, ratio: ratio);
await uploadAttachment(
await file.readAsBytes(),
file.path,
hash,
ratio: ratio,
);
} catch (err) {
context.showErrorDialog(err);
}
@ -142,11 +153,13 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
setState(() => _isBusy = false);
}
Future<void> uploadAttachment(File file, String hash, {double? ratio}) async {
Future<void> uploadAttachment(Uint8List data, String path, String hash,
{double? ratio}) async {
final AttachmentProvider provider = Get.find();
try {
final resp = await provider.createAttachment(
file,
data,
path,
hash,
widget.usage,
ratio: ratio,
@ -208,10 +221,14 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
@override
void initState() {
super.initState();
revertMetadataList();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
const density = VisualDensity(horizontal: 0, vertical: 0);
@ -219,6 +236,36 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
return SafeArea(
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.85,
child: DropRegion(
formats: Formats.standardFormats,
hitTestBehavior: HitTestBehavior.opaque,
onDropOver: (event) {
if (event.session.allowedOperations.contains(DropOperation.copy)) {
return DropOperation.copy;
} else {
return DropOperation.none;
}
},
onPerformDrop: (event) async {
for (final item in event.session.items) {
final reader = item.dataReader!;
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 ?? 'attachment',
await calculateBytesSha256(data),
);
}, onError: (error) {
print('Error reading value $error');
});
}
}
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -242,8 +289,8 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
var fileType = element!.mimetype.split('/').firstOrNull;
fileType ??= 'unknown';
return Container(
padding:
const EdgeInsets.only(left: 16, right: 8, bottom: 16),
padding: const EdgeInsets.only(
left: 16, right: 8, bottom: 16),
child: Row(
children: [
Expanded(
@ -349,6 +396,7 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
],
),
),
),
);
}
}
@ -365,8 +413,7 @@ class AttachmentEditorDialog extends StatefulWidget {
required this.onUpdate});
@override
State<AttachmentEditorDialog> createState() =>
_AttachmentEditorDialogState();
State<AttachmentEditorDialog> createState() => _AttachmentEditorDialogState();
}
class _AttachmentEditorDialogState extends State<AttachmentEditorDialog> {

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:solian/controllers/chat_events_controller.dart';

View File

@ -10,7 +10,9 @@
#include <flutter_acrylic/flutter_acrylic_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <irondash_engine_context/irondash_engine_context_plugin.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <super_native_extensions/super_native_extensions_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
@ -26,9 +28,15 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar);
g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin");
irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar);
g_autoptr(FlPluginRegistrar) sentry_flutter_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar);
g_autoptr(FlPluginRegistrar) super_native_extensions_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin");
super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@ -7,7 +7,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_acrylic
flutter_secure_storage_linux
flutter_webrtc
irondash_engine_context
sentry_flutter
super_native_extensions
url_launcher_linux
)

View File

@ -13,6 +13,7 @@ import firebase_messaging
import flutter_local_notifications
import flutter_secure_storage_macos
import flutter_webrtc
import irondash_engine_context
import livekit_client
import macos_window_utils
import package_info_plus
@ -21,6 +22,7 @@ import protocol_handler_macos
import sentry_flutter
import shared_preferences_foundation
import sqflite
import super_native_extensions
import url_launcher_macos
import video_player_avfoundation
import wakelock_plus
@ -34,6 +36,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
@ -42,6 +45,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))

View File

@ -824,6 +824,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
irondash_engine_context:
dependency: transitive
description:
name: irondash_engine_context
sha256: cd7b769db11a2b5243b037c8a9b1ecaef02e1ae27a2d909ffa78c1dad747bb10
url: "https://pub.dev"
source: hosted
version: "0.5.4"
irondash_message_channel:
dependency: transitive
description:
name: irondash_message_channel
sha256: b4101669776509c76133b8917ab8cfc704d3ad92a8c450b92934dd8884a2f060
url: "https://pub.dev"
source: hosted
version: "0.7.0"
js:
dependency: transitive
description:
@ -1104,6 +1120,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.2"
pixel_snap:
dependency: transitive
description:
name: pixel_snap
sha256: "677410ea37b07cd37ecb6d5e6c0d8d7615a7cf3bd92ba406fd1ac57e937d1fb0"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
platform:
dependency: transitive
description:
@ -1453,6 +1477,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.2"
super_clipboard:
dependency: transitive
description:
name: super_clipboard
sha256: cdab725bac26655ebd189f4d202d694a8cbc1c21e0f0478ccd7829c71716f09b
url: "https://pub.dev"
source: hosted
version: "0.8.17"
super_drag_and_drop:
dependency: "direct main"
description:
name: super_drag_and_drop
sha256: "8e00c6082646076f80b972b39d9c27b5311082ea1e8add5fa370ce11c410f7de"
url: "https://pub.dev"
source: hosted
version: "0.8.17"
super_native_extensions:
dependency: transitive
description:
name: super_native_extensions
sha256: fa55d452d34b7112453afbb9fa4d13c0527ff201630d10d86546497179030544
url: "https://pub.dev"
source: hosted
version: "0.8.17"
synchronized:
dependency: transitive
description:

View File

@ -51,6 +51,7 @@ dependencies:
sqflite: ^2.3.3+1
protocol_handler: ^0.2.0
markdown: ^7.2.2
super_drag_and_drop: ^0.8.17
dev_dependencies:
flutter_test:

View File

@ -12,10 +12,12 @@
#include <flutter_acrylic/flutter_acrylic_plugin.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <irondash_engine_context/irondash_engine_context_plugin_c_api.h>
#include <livekit_client/live_kit_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <protocol_handler_windows/protocol_handler_windows_plugin_c_api.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <super_native_extensions/super_native_extensions_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <video_player_win/video_player_win_plugin_c_api.h>
@ -32,6 +34,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
FlutterWebRTCPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
IrondashEngineContextPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi"));
LiveKitPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LiveKitPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
@ -40,6 +44,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi"));
SentryFlutterPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SentryFlutterPlugin"));
SuperNativeExtensionsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
VideoPlayerWinPluginCApiRegisterWithRegistrar(

View File

@ -9,10 +9,12 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_acrylic
flutter_secure_storage_windows
flutter_webrtc
irondash_engine_context
livekit_client
permission_handler_windows
protocol_handler_windows
sentry_flutter
super_native_extensions
url_launcher_windows
video_player_win
)