✨ Drag to upload
This commit is contained in:
parent
85bba21285
commit
e336d2372a
@ -30,6 +30,13 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="com.superlist.super_native_extensions.DataProvider"
|
||||||
|
android:authorities="dev.solsynth.solian.SuperClipboardDataProvider"
|
||||||
|
android:exported="true"
|
||||||
|
android:grantUriPermissions="true" >
|
||||||
|
</provider>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
@ -33,7 +33,9 @@ void main() async {
|
|||||||
appRunner: () async {
|
appRunner: () async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
await protocolHandler.register('solink');
|
if (!PlatformInfo.isWeb) {
|
||||||
|
await protocolHandler.register('solink');
|
||||||
|
}
|
||||||
|
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
@ -179,7 +179,7 @@ class AccountProvider extends GetxController {
|
|||||||
|
|
||||||
Future<void> registerPushNotifications() async {
|
Future<void> registerPushNotifications() async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (!await auth.isAuthorized) throw Exception('unauthorized');
|
if (!await auth.isAuthorized) return;
|
||||||
|
|
||||||
late final String? token;
|
late final String? token;
|
||||||
late final String provider;
|
late final String provider;
|
||||||
|
@ -1,21 +1,40 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
|
import 'package:solian/platform.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
import 'package:image/image.dart' as img;
|
import 'package:image/image.dart' as img;
|
||||||
|
|
||||||
Future<String> calculateFileSha256(File file) async {
|
Future<String> calculateBytesSha256(Uint8List data) async {
|
||||||
final bytes = await Isolate.run(() => file.readAsBytesSync());
|
Digest digest;
|
||||||
final digest = await Isolate.run(() => sha256.convert(bytes));
|
if (PlatformInfo.isWeb) {
|
||||||
|
digest = sha256.convert(data);
|
||||||
|
} else {
|
||||||
|
digest = await Isolate.run(() => sha256.convert(data));
|
||||||
|
}
|
||||||
return digest.toString();
|
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 {
|
Future<double> calculateFileAspectRatio(File file) async {
|
||||||
|
if (PlatformInfo.isWeb) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
final bytes = await Isolate.run(() => file.readAsBytesSync());
|
final bytes = await Isolate.run(() => file.readAsBytesSync());
|
||||||
final decoder = await Isolate.run(() => img.findDecoderForData(bytes));
|
final decoder = await Isolate.run(() => img.findDecoderForData(bytes));
|
||||||
if (decoder == null) return 1;
|
if (decoder == null) return 1;
|
||||||
@ -25,7 +44,10 @@ Future<double> calculateFileAspectRatio(File file) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AttachmentProvider extends GetConnect {
|
class AttachmentProvider extends GetConnect {
|
||||||
static Map<String, String> mimetypeOverrides = {'mov': 'video/quicktime'};
|
static Map<String, String> mimetypeOverrides = {
|
||||||
|
'mov': 'video/quicktime',
|
||||||
|
'mp4': 'video/mp4'
|
||||||
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -45,7 +67,8 @@ class AttachmentProvider extends GetConnect {
|
|||||||
return resp;
|
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 {
|
{double? ratio}) async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (!await auth.isAuthorized) throw Exception('unauthorized');
|
if (!await auth.isAuthorized) throw Exception('unauthorized');
|
||||||
@ -55,13 +78,12 @@ class AttachmentProvider extends GetConnect {
|
|||||||
timeout: const Duration(minutes: 3),
|
timeout: const Duration(minutes: 3),
|
||||||
);
|
);
|
||||||
|
|
||||||
final filePayload =
|
final filePayload = MultipartFile(data, filename: basename(path));
|
||||||
MultipartFile(await file.readAsBytes(), filename: basename(file.path));
|
final fileAlt = basename(path).contains('.')
|
||||||
final fileAlt = basename(file.path).contains('.')
|
? basename(path).substring(0, basename(path).lastIndexOf('.'))
|
||||||
? basename(file.path).substring(0, basename(file.path).lastIndexOf('.'))
|
: basename(path);
|
||||||
: basename(file.path);
|
final fileExt = basename(path)
|
||||||
final fileExt = basename(file.path)
|
.substring(basename(path).lastIndexOf('.') + 1)
|
||||||
.substring(basename(file.path).lastIndexOf('.') + 1)
|
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
|
|
||||||
// Override for some files cannot be detected mimetype by server-side
|
// Override for some files cannot be detected mimetype by server-side
|
||||||
|
@ -88,7 +88,8 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
|
|||||||
final file = File(image.path);
|
final file = File(image.path);
|
||||||
final hash = await calculateFileSha256(file);
|
final hash = await calculateFileSha256(file);
|
||||||
attachResp = await provider.createAttachment(
|
attachResp = await provider.createAttachment(
|
||||||
file,
|
await file.readAsBytes(),
|
||||||
|
file.path,
|
||||||
hash,
|
hash,
|
||||||
'p.$position',
|
'p.$position',
|
||||||
ratio: await calculateFileAspectRatio(file),
|
ratio: await calculateFileAspectRatio(file),
|
||||||
|
@ -178,12 +178,6 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
getMetadataList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.attachmentsId.isEmpty) {
|
if (widget.attachmentsId.isEmpty) {
|
||||||
@ -236,7 +230,8 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
const radius = BorderRadius.all(Radius.circular(16));
|
const radius = BorderRadius.all(Radius.circular(16));
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: Theme.of(context).dividerColor, width: 1),
|
border:
|
||||||
|
Border.all(color: Theme.of(context).dividerColor, width: 1),
|
||||||
borderRadius: radius,
|
borderRadius: radius,
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.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/models/attachment.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/providers/content/attachment.dart';
|
import 'package:solian/providers/content/attachment.dart';
|
||||||
|
import 'package:super_drag_and_drop/super_drag_and_drop.dart';
|
||||||
|
|
||||||
class AttachmentPublishPopup extends StatefulWidget {
|
class AttachmentPublishPopup extends StatefulWidget {
|
||||||
final String usage;
|
final String usage;
|
||||||
@ -24,8 +29,7 @@ class AttachmentPublishPopup extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AttachmentPublishPopup> createState() =>
|
State<AttachmentPublishPopup> createState() => _AttachmentPublishPopupState();
|
||||||
_AttachmentPublishPopupState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
||||||
@ -51,7 +55,8 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await uploadAttachment(
|
await uploadAttachment(
|
||||||
file,
|
await file.readAsBytes(),
|
||||||
|
file.path,
|
||||||
hash,
|
hash,
|
||||||
ratio: await calculateFileAspectRatio(file),
|
ratio: await calculateFileAspectRatio(file),
|
||||||
);
|
);
|
||||||
@ -77,7 +82,8 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
|||||||
const ratio = 16 / 9;
|
const ratio = 16 / 9;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await uploadAttachment(file, hash, ratio: ratio);
|
await uploadAttachment(await file.readAsBytes(), file.path, hash,
|
||||||
|
ratio: ratio);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
}
|
}
|
||||||
@ -100,7 +106,7 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
|||||||
for (final file in files) {
|
for (final file in files) {
|
||||||
final hash = await calculateFileSha256(file);
|
final hash = await calculateFileSha256(file);
|
||||||
try {
|
try {
|
||||||
await uploadAttachment(file, hash);
|
await uploadAttachment(await file.readAsBytes(), file.path, hash);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
}
|
}
|
||||||
@ -134,7 +140,12 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await uploadAttachment(file, hash, ratio: ratio);
|
await uploadAttachment(
|
||||||
|
await file.readAsBytes(),
|
||||||
|
file.path,
|
||||||
|
hash,
|
||||||
|
ratio: ratio,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
}
|
}
|
||||||
@ -142,11 +153,13 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
|||||||
setState(() => _isBusy = false);
|
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();
|
final AttachmentProvider provider = Get.find();
|
||||||
try {
|
try {
|
||||||
final resp = await provider.createAttachment(
|
final resp = await provider.createAttachment(
|
||||||
file,
|
data,
|
||||||
|
path,
|
||||||
hash,
|
hash,
|
||||||
widget.usage,
|
widget.usage,
|
||||||
ratio: ratio,
|
ratio: ratio,
|
||||||
@ -208,10 +221,14 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
revertMetadataList();
|
revertMetadataList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const density = VisualDensity(horizontal: 0, vertical: 0);
|
const density = VisualDensity(horizontal: 0, vertical: 0);
|
||||||
@ -219,134 +236,165 @@ class _AttachmentPublishPopupState extends State<AttachmentPublishPopup> {
|
|||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: MediaQuery.of(context).size.height * 0.85,
|
height: MediaQuery.of(context).size.height * 0.85,
|
||||||
child: Column(
|
child: DropRegion(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
formats: Formats.standardFormats,
|
||||||
children: [
|
hitTestBehavior: HitTestBehavior.opaque,
|
||||||
Text(
|
onDropOver: (event) {
|
||||||
'attachmentAdd'.tr,
|
if (event.session.allowedOperations.contains(DropOperation.copy)) {
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
return DropOperation.copy;
|
||||||
).paddingOnly(left: 24, right: 24, top: 32, bottom: 16),
|
} else {
|
||||||
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
|
return DropOperation.none;
|
||||||
Expanded(
|
}
|
||||||
child: Builder(builder: (context) {
|
},
|
||||||
if (_isFirstTimeBusy && _isBusy) {
|
onPerformDrop: (event) async {
|
||||||
return const Center(
|
for (final item in event.session.items) {
|
||||||
child: CircularProgressIndicator(),
|
final reader = item.dataReader!;
|
||||||
);
|
for (final format
|
||||||
}
|
in Formats.standardFormats.whereType<FileFormat>()) {
|
||||||
|
if (reader.canProvide(format)) {
|
||||||
return ListView.builder(
|
reader.getFile(format, (file) async {
|
||||||
itemCount: _attachments.length,
|
final data = await file.readAll();
|
||||||
itemBuilder: (context, index) {
|
await uploadAttachment(
|
||||||
final element = _attachments[index];
|
data,
|
||||||
var fileType = element!.mimetype.split('/').firstOrNull;
|
file.fileName ?? 'attachment',
|
||||||
fileType ??= 'unknown';
|
await calculateBytesSha256(data),
|
||||||
return Container(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.only(left: 16, right: 8, bottom: 16),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
element.alt,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'${fileType[0].toUpperCase()}${fileType.substring(1)} · ${formatBytes(element.size)}',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
shape: const CircleBorder(),
|
|
||||||
foregroundColor: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
icon: const Icon(Icons.more_horiz),
|
|
||||||
onPressed: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return AttachmentEditorDialog(
|
|
||||||
item: element,
|
|
||||||
onDelete: () {
|
|
||||||
setState(
|
|
||||||
() => _attachments.removeAt(index));
|
|
||||||
widget.onUpdate(_attachments
|
|
||||||
.map((e) => e!.id)
|
|
||||||
.toList());
|
|
||||||
},
|
|
||||||
onUpdate: (item) {
|
|
||||||
setState(
|
|
||||||
() => _attachments[index] = item);
|
|
||||||
widget.onUpdate(_attachments
|
|
||||||
.map((e) => e!.id)
|
|
||||||
.toList());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
}, onError: (error) {
|
||||||
);
|
print('Error reading value $error');
|
||||||
}),
|
});
|
||||||
),
|
}
|
||||||
const Divider(thickness: 0.3, height: 0.3),
|
}
|
||||||
SizedBox(
|
}
|
||||||
height: 64,
|
},
|
||||||
child: SingleChildScrollView(
|
child: Column(
|
||||||
scrollDirection: Axis.horizontal,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Wrap(
|
children: [
|
||||||
spacing: 8,
|
Text(
|
||||||
runSpacing: 0,
|
'attachmentAdd'.tr,
|
||||||
alignment: WrapAlignment.center,
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
runAlignment: WrapAlignment.center,
|
).paddingOnly(left: 24, right: 24, top: 32, bottom: 16),
|
||||||
children: [
|
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
|
||||||
ElevatedButton.icon(
|
Expanded(
|
||||||
icon: const Icon(Icons.add_photo_alternate),
|
child: Builder(builder: (context) {
|
||||||
label: Text('attachmentAddGalleryPhoto'.tr),
|
if (_isFirstTimeBusy && _isBusy) {
|
||||||
style: const ButtonStyle(visualDensity: density),
|
return const Center(
|
||||||
onPressed: () => pickPhotoToUpload(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
);
|
||||||
ElevatedButton.icon(
|
}
|
||||||
icon: const Icon(Icons.add_road),
|
|
||||||
label: Text('attachmentAddGalleryVideo'.tr),
|
return ListView.builder(
|
||||||
style: const ButtonStyle(visualDensity: density),
|
itemCount: _attachments.length,
|
||||||
onPressed: () => pickVideoToUpload(),
|
itemBuilder: (context, index) {
|
||||||
),
|
final element = _attachments[index];
|
||||||
ElevatedButton.icon(
|
var fileType = element!.mimetype.split('/').firstOrNull;
|
||||||
icon: const Icon(Icons.photo_camera_back),
|
fileType ??= 'unknown';
|
||||||
label: Text('attachmentAddCameraPhoto'.tr),
|
return Container(
|
||||||
style: const ButtonStyle(visualDensity: density),
|
padding: const EdgeInsets.only(
|
||||||
onPressed: () => takeMediaToUpload(false),
|
left: 16, right: 8, bottom: 16),
|
||||||
),
|
child: Row(
|
||||||
ElevatedButton.icon(
|
children: [
|
||||||
icon: const Icon(Icons.video_camera_back_outlined),
|
Expanded(
|
||||||
label: Text('attachmentAddCameraVideo'.tr),
|
child: Column(
|
||||||
style: const ButtonStyle(visualDensity: density),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
onPressed: () => takeMediaToUpload(true),
|
children: [
|
||||||
),
|
Text(
|
||||||
ElevatedButton.icon(
|
element.alt,
|
||||||
icon: const Icon(Icons.file_present_rounded),
|
overflow: TextOverflow.ellipsis,
|
||||||
label: Text('attachmentAddFile'.tr),
|
maxLines: 1,
|
||||||
style: const ButtonStyle(visualDensity: density),
|
style: const TextStyle(
|
||||||
onPressed: () => pickFileToUpload(),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
],
|
Text(
|
||||||
).paddingSymmetric(horizontal: 12),
|
'${fileType[0].toUpperCase()}${fileType.substring(1)} · ${formatBytes(element.size)}',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
foregroundColor: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.more_horiz),
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AttachmentEditorDialog(
|
||||||
|
item: element,
|
||||||
|
onDelete: () {
|
||||||
|
setState(
|
||||||
|
() => _attachments.removeAt(index));
|
||||||
|
widget.onUpdate(_attachments
|
||||||
|
.map((e) => e!.id)
|
||||||
|
.toList());
|
||||||
|
},
|
||||||
|
onUpdate: (item) {
|
||||||
|
setState(
|
||||||
|
() => _attachments[index] = item);
|
||||||
|
widget.onUpdate(_attachments
|
||||||
|
.map((e) => e!.id)
|
||||||
|
.toList());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
)
|
const Divider(thickness: 0.3, height: 0.3),
|
||||||
],
|
SizedBox(
|
||||||
|
height: 64,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 0,
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
runAlignment: WrapAlignment.center,
|
||||||
|
children: [
|
||||||
|
ElevatedButton.icon(
|
||||||
|
icon: const Icon(Icons.add_photo_alternate),
|
||||||
|
label: Text('attachmentAddGalleryPhoto'.tr),
|
||||||
|
style: const ButtonStyle(visualDensity: density),
|
||||||
|
onPressed: () => pickPhotoToUpload(),
|
||||||
|
),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
icon: const Icon(Icons.add_road),
|
||||||
|
label: Text('attachmentAddGalleryVideo'.tr),
|
||||||
|
style: const ButtonStyle(visualDensity: density),
|
||||||
|
onPressed: () => pickVideoToUpload(),
|
||||||
|
),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
icon: const Icon(Icons.photo_camera_back),
|
||||||
|
label: Text('attachmentAddCameraPhoto'.tr),
|
||||||
|
style: const ButtonStyle(visualDensity: density),
|
||||||
|
onPressed: () => takeMediaToUpload(false),
|
||||||
|
),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
icon: const Icon(Icons.video_camera_back_outlined),
|
||||||
|
label: Text('attachmentAddCameraVideo'.tr),
|
||||||
|
style: const ButtonStyle(visualDensity: density),
|
||||||
|
onPressed: () => takeMediaToUpload(true),
|
||||||
|
),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
icon: const Icon(Icons.file_present_rounded),
|
||||||
|
label: Text('attachmentAddFile'.tr),
|
||||||
|
style: const ButtonStyle(visualDensity: density),
|
||||||
|
onPressed: () => pickFileToUpload(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddingSymmetric(horizontal: 12),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -365,8 +413,7 @@ class AttachmentEditorDialog extends StatefulWidget {
|
|||||||
required this.onUpdate});
|
required this.onUpdate});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AttachmentEditorDialog> createState() =>
|
State<AttachmentEditorDialog> createState() => _AttachmentEditorDialogState();
|
||||||
_AttachmentEditorDialogState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AttachmentEditorDialogState extends State<AttachmentEditorDialog> {
|
class _AttachmentEditorDialogState extends State<AttachmentEditorDialog> {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/controllers/chat_events_controller.dart';
|
import 'package:solian/controllers/chat_events_controller.dart';
|
||||||
|
@ -10,7 +10,9 @@
|
|||||||
#include <flutter_acrylic/flutter_acrylic_plugin.h>
|
#include <flutter_acrylic/flutter_acrylic_plugin.h>
|
||||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
#include <flutter_webrtc/flutter_web_r_t_c_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 <sentry_flutter/sentry_flutter_plugin.h>
|
||||||
|
#include <super_native_extensions/super_native_extensions_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
@ -26,9 +28,15 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
|
||||||
flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar);
|
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 =
|
g_autoptr(FlPluginRegistrar) sentry_flutter_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
|
||||||
sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar);
|
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 =
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
|
@ -7,7 +7,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
flutter_acrylic
|
flutter_acrylic
|
||||||
flutter_secure_storage_linux
|
flutter_secure_storage_linux
|
||||||
flutter_webrtc
|
flutter_webrtc
|
||||||
|
irondash_engine_context
|
||||||
sentry_flutter
|
sentry_flutter
|
||||||
|
super_native_extensions
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import firebase_messaging
|
|||||||
import flutter_local_notifications
|
import flutter_local_notifications
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import flutter_webrtc
|
import flutter_webrtc
|
||||||
|
import irondash_engine_context
|
||||||
import livekit_client
|
import livekit_client
|
||||||
import macos_window_utils
|
import macos_window_utils
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
@ -21,6 +22,7 @@ import protocol_handler_macos
|
|||||||
import sentry_flutter
|
import sentry_flutter
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite
|
import sqflite
|
||||||
|
import super_native_extensions
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
import video_player_avfoundation
|
import video_player_avfoundation
|
||||||
import wakelock_plus
|
import wakelock_plus
|
||||||
@ -34,6 +36,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||||
|
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
|
||||||
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
||||||
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
|
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
|
||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
@ -42,6 +45,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
|
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
|
SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
||||||
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
||||||
|
48
pubspec.lock
48
pubspec.lock
@ -824,6 +824,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
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:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1104,6 +1120,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.2"
|
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:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1453,6 +1477,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
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:
|
synchronized:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -51,6 +51,7 @@ dependencies:
|
|||||||
sqflite: ^2.3.3+1
|
sqflite: ^2.3.3+1
|
||||||
protocol_handler: ^0.2.0
|
protocol_handler: ^0.2.0
|
||||||
markdown: ^7.2.2
|
markdown: ^7.2.2
|
||||||
|
super_drag_and_drop: ^0.8.17
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -12,10 +12,12 @@
|
|||||||
#include <flutter_acrylic/flutter_acrylic_plugin.h>
|
#include <flutter_acrylic/flutter_acrylic_plugin.h>
|
||||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
#include <flutter_webrtc/flutter_web_r_t_c_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 <livekit_client/live_kit_plugin.h>
|
||||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
#include <protocol_handler_windows/protocol_handler_windows_plugin_c_api.h>
|
#include <protocol_handler_windows/protocol_handler_windows_plugin_c_api.h>
|
||||||
#include <sentry_flutter/sentry_flutter_plugin.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 <url_launcher_windows/url_launcher_windows.h>
|
||||||
#include <video_player_win/video_player_win_plugin_c_api.h>
|
#include <video_player_win/video_player_win_plugin_c_api.h>
|
||||||
|
|
||||||
@ -32,6 +34,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
FlutterWebRTCPluginRegisterWithRegistrar(
|
FlutterWebRTCPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
|
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
|
||||||
|
IrondashEngineContextPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi"));
|
||||||
LiveKitPluginRegisterWithRegistrar(
|
LiveKitPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("LiveKitPlugin"));
|
registry->GetRegistrarForPlugin("LiveKitPlugin"));
|
||||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||||
@ -40,6 +44,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi"));
|
registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi"));
|
||||||
SentryFlutterPluginRegisterWithRegistrar(
|
SentryFlutterPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("SentryFlutterPlugin"));
|
registry->GetRegistrarForPlugin("SentryFlutterPlugin"));
|
||||||
|
SuperNativeExtensionsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
VideoPlayerWinPluginCApiRegisterWithRegistrar(
|
VideoPlayerWinPluginCApiRegisterWithRegistrar(
|
||||||
|
@ -9,10 +9,12 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
flutter_acrylic
|
flutter_acrylic
|
||||||
flutter_secure_storage_windows
|
flutter_secure_storage_windows
|
||||||
flutter_webrtc
|
flutter_webrtc
|
||||||
|
irondash_engine_context
|
||||||
livekit_client
|
livekit_client
|
||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
protocol_handler_windows
|
protocol_handler_windows
|
||||||
sentry_flutter
|
sentry_flutter
|
||||||
|
super_native_extensions
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
video_player_win
|
video_player_win
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user