Better attachments upload, and supports files

This commit is contained in:
LittleSheep 2024-05-03 00:09:33 +08:00
parent 9f6942a8cb
commit ec69b34877
12 changed files with 176 additions and 101 deletions

View File

@ -4,6 +4,40 @@ PODS:
- FlutterMacOS - FlutterMacOS
- device_info_plus (0.0.1): - device_info_plus (0.0.1):
- Flutter - Flutter
- DKImagePickerController/Core (4.3.8):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.8)
- DKImagePickerController/PhotoGallery (4.3.8):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.8)
- DKPhotoGallery (0.0.17):
- DKPhotoGallery/Core (= 0.0.17)
- DKPhotoGallery/Model (= 0.0.17)
- DKPhotoGallery/Preview (= 0.0.17)
- DKPhotoGallery/Resource (= 0.0.17)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.17):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.17):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_local_notifications (0.0.1): - flutter_local_notifications (0.0.1):
- Flutter - Flutter
@ -32,6 +66,10 @@ PODS:
- Flutter - Flutter
- screen_brightness_ios (0.1.0): - screen_brightness_ios (0.1.0):
- Flutter - Flutter
- SDWebImage (5.19.1):
- SDWebImage/Core (= 5.19.1)
- SDWebImage/Core (5.19.1)
- SwiftyGif (5.4.5)
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
- volume_controller (0.0.1): - volume_controller (0.0.1):
@ -45,6 +83,7 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
@ -65,6 +104,10 @@ DEPENDENCIES:
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- DKImagePickerController
- DKPhotoGallery
- SDWebImage
- SwiftyGif
- WebRTC-SDK - WebRTC-SDK
EXTERNAL SOURCES: EXTERNAL SOURCES:
@ -72,6 +115,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/connectivity_plus/darwin" :path: ".symlinks/plugins/connectivity_plus/darwin"
device_info_plus: device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios" :path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_local_notifications: flutter_local_notifications:
@ -110,6 +155,9 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
DKImagePickerController: a7836546cfdfe014171694f643a7d575bc8ace7f
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
@ -123,6 +171,8 @@ SPEC CHECKSUMS:
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
SDWebImage: 40b0b4053e36c660a764958bff99eed16610acbb
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586 url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9 volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1

View File

@ -59,10 +59,10 @@
"comment": "Comment", "comment": "Comment",
"attachment": "Attachment", "attachment": "Attachment",
"attachmentAdd": "Add new attachment", "attachmentAdd": "Add new attachment",
"pickPhoto": "Gallery photo", "pickMedia": "Gallery media",
"pickFile": "Device file",
"takePhoto": "Capture photo", "takePhoto": "Capture photo",
"pickVideo": "Gallery video", "takeVideo": "Capture video",
"takeVideo": "Record video",
"newMoment": "Record a moment", "newMoment": "Record a moment",
"newComment": "Leave a comment", "newComment": "Leave a comment",
"connectingServer": "Connecting to server...", "connectingServer": "Connecting to server...",

View File

@ -59,9 +59,9 @@
"comment": "评论", "comment": "评论",
"attachment": "附件", "attachment": "附件",
"attachmentAdd": "附加新附件", "attachmentAdd": "附加新附件",
"pickPhoto": "相册照片", "pickMedia": "相册媒体",
"pickFile": "设备文件",
"takePhoto": "拍摄照片", "takePhoto": "拍摄照片",
"pickVideo": "相册视频",
"takeVideo": "拍摄视频", "takeVideo": "拍摄视频",
"newMoment": "记录时刻", "newMoment": "记录时刻",
"newComment": "留下评论", "newComment": "留下评论",

View File

@ -107,6 +107,12 @@ class ChatProvider extends ChangeNotifier {
isCallShown = state; isCallShown = state;
notifyListeners(); notifyListeners();
} }
void unFocus() {
currentCall = null;
focusChannel = null;
notifyListeners();
}
} }
class ChatCallInstance { class ChatCallInstance {

View File

@ -86,7 +86,7 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final editingBanner = MaterialBanner( final editingBanner = MaterialBanner(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20), padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
leading: const Icon(Icons.edit_note), leading: const Icon(Icons.edit_note),
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9), backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
dividerColor: const Color.fromARGB(1, 0, 0, 0), dividerColor: const Color.fromARGB(1, 0, 0, 0),
@ -111,6 +111,7 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
child: Column( child: Column(
children: [ children: [
_isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
widget.editing != null ? editingBanner : Container(),
ListTile( ListTile(
title: Text(AppLocalizations.of(context)!.chatChannelUsage), title: Text(AppLocalizations.of(context)!.chatChannelUsage),
subtitle: Text(AppLocalizations.of(context)!.chatChannelUsageCaption), subtitle: Text(AppLocalizations.of(context)!.chatChannelUsageCaption),
@ -174,7 +175,6 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
), ),
), ),
), ),
widget.editing != null ? editingBanner : Container(),
], ],
), ),
); );

View File

@ -249,8 +249,7 @@ class _ChatScreenWidgetState extends State<ChatScreenWidget> {
@override @override
void deactivate() { void deactivate() {
_chat.focusChannel = null; _chat.unFocus();
_chat.ongoingCall = null;
super.deactivate(); super.deactivate();
} }
} }

View File

@ -107,7 +107,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
final auth = context.read<AuthProvider>(); final auth = context.read<AuthProvider>();
final editingBanner = MaterialBanner( final editingBanner = MaterialBanner(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20), padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
leading: const Icon(Icons.edit_note), leading: const Icon(Icons.edit_note),
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9), backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
dividerColor: const Color.fromARGB(1, 0, 0, 0), dividerColor: const Color.fromARGB(1, 0, 0, 0),
@ -175,6 +175,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
), ),
widget.editing != null ? editingBanner : Container(), widget.editing != null ? editingBanner : Container(),
Container( Container(
constraints: const BoxConstraints(minHeight: 56),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
top: BorderSide(width: 0.3, color: Theme.of(context).dividerColor), top: BorderSide(width: 0.3, color: Theme.of(context).dividerColor),

View File

@ -97,7 +97,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
final auth = context.read<AuthProvider>(); final auth = context.read<AuthProvider>();
final editingBanner = MaterialBanner( final editingBanner = MaterialBanner(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20), padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
leading: const Icon(Icons.edit_note), leading: const Icon(Icons.edit_note),
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9), backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
dividerColor: const Color.fromARGB(1, 0, 0, 0), dividerColor: const Color.fromARGB(1, 0, 0, 0),
@ -121,9 +121,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
], ],
child: Column( child: Column(
children: [ children: [
_isSubmitting _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
? const LinearProgressIndicator().animate().scaleX()
: Container(),
FutureBuilder( FutureBuilder(
future: auth.getProfiles(), future: auth.getProfiles(),
builder: (context, snapshot) { builder: (context, snapshot) {
@ -155,16 +153,15 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
controller: _textController, controller: _textController,
decoration: InputDecoration.collapsed( decoration: InputDecoration.collapsed(
hintText: hintText: AppLocalizations.of(context)!.postContentPlaceholder,
AppLocalizations.of(context)!.postContentPlaceholder,
), ),
onTapOutside: (_) => onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
FocusManager.instance.primaryFocus?.unfocus(),
), ),
), ),
), ),
widget.editing != null ? editingBanner : Container(), widget.editing != null ? editingBanner : Container(),
Container( Container(
constraints: const BoxConstraints(minHeight: 56),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
top: BorderSide(width: 0.3, color: Theme.of(context).dividerColor), top: BorderSide(width: 0.3, color: Theme.of(context).dividerColor),

View File

@ -106,7 +106,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final editingBanner = MaterialBanner( final editingBanner = MaterialBanner(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 20), padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
leading: const Icon(Icons.edit_note), leading: const Icon(Icons.edit_note),
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9), backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
dividerColor: const Color.fromARGB(1, 0, 0, 0), dividerColor: const Color.fromARGB(1, 0, 0, 0),
@ -120,7 +120,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
); );
final replyingBanner = MaterialBanner( final replyingBanner = MaterialBanner(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 20), padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
leading: const Icon(Icons.reply), leading: const Icon(Icons.reply),
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9), backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
dividerColor: const Color.fromARGB(1, 0, 0, 0), dividerColor: const Color.fromARGB(1, 0, 0, 0),

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.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';
import 'package:http/http.dart'; import 'package:http/http.dart';
@ -41,51 +42,84 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) => AttachmentEditorMethodPopup( builder: (context) => AttachmentEditorMethodPopup(
pickImage: () => pickImageToUpload(context, ImageSource.gallery), pickMedia: () => pickMediaToUpload(context),
takeImage: () => pickImageToUpload(context, ImageSource.camera), pickFile: () => pickFileToUpload(context),
pickVideo: () => pickVideoToUpload(context, ImageSource.gallery), takeImage: () => takeMediaToUpload(context, false),
takeVideo: () => pickVideoToUpload(context, ImageSource.camera), takeVideo: () => takeMediaToUpload(context, true),
), ),
); );
} }
Future<void> pickImageToUpload( Future<void> pickMediaToUpload(BuildContext context) async {
BuildContext context, ImageSource source) async {
final auth = context.read<AuthProvider>(); final auth = context.read<AuthProvider>();
if (!await auth.isAuthorized()) return; if (!await auth.isAuthorized()) return;
final image = await _imagePicker.pickImage(source: source); final medias = await _imagePicker.pickMultipleMedia();
if (image == null) return; if (medias.isEmpty) return;
setState(() => _isSubmitting = true); setState(() => _isSubmitting = true);
final file = File(image.path); bool isPopped = false;
final hashcode = await calculateSha256(file); for (final media in medias) {
final file = File(media.path);
if (Navigator.canPop(context)) { final hashcode = await calculateSha256(file);
Navigator.pop(context); try {
await uploadAttachment(file, hashcode);
} catch (err) {
context.showErrorDialog(err);
}
if (!isPopped && Navigator.canPop(context)) {
Navigator.pop(context);
isPopped = true;
}
} }
try { setState(() => _isSubmitting = false);
await uploadAttachment(file, hashcode);
} catch (err) {
context.showErrorDialog(err);
} finally {
setState(() => _isSubmitting = false);
}
} }
Future<void> pickVideoToUpload( Future<void> pickFileToUpload(BuildContext context) async {
BuildContext context, ImageSource source) async {
final auth = context.read<AuthProvider>(); final auth = context.read<AuthProvider>();
if (!await auth.isAuthorized()) return; if (!await auth.isAuthorized()) return;
final image = await _imagePicker.pickVideo(source: source); FilePickerResult? result = await FilePicker.platform.pickFiles(allowMultiple: true);
if (image == null) return; if (result == null) return;
List<File> files = result.paths.map((path) => File(path!)).toList();
setState(() => _isSubmitting = true); setState(() => _isSubmitting = true);
final file = File(image.path); bool isPopped = false;
for (final file in files) {
final hashcode = await calculateSha256(file);
try {
await uploadAttachment(file, hashcode);
} catch (err) {
context.showErrorDialog(err);
}
if (!isPopped && Navigator.canPop(context)) {
Navigator.pop(context);
isPopped = true;
}
}
setState(() => _isSubmitting = false);
}
Future<void> takeMediaToUpload(BuildContext context, bool isVideo) async {
final auth = context.read<AuthProvider>();
if (!await auth.isAuthorized()) return;
XFile? media;
if (isVideo) {
media = await _imagePicker.pickVideo(source: ImageSource.camera);
} else {
media = await _imagePicker.pickImage(source: ImageSource.camera);
}
if (media == null) return;
setState(() => _isSubmitting = true);
final file = File(media.path);
final hashcode = await calculateSha256(file); final hashcode = await calculateSha256(file);
if (Navigator.canPop(context)) { if (Navigator.canPop(context)) {
@ -96,16 +130,15 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
await uploadAttachment(file, hashcode); await uploadAttachment(file, hashcode);
} catch (err) { } catch (err) {
context.showErrorDialog(err); context.showErrorDialog(err);
} finally {
setState(() => _isSubmitting = false);
} }
setState(() => _isSubmitting = false);
} }
Future<void> uploadAttachment(File file, String hashcode) async { Future<void> uploadAttachment(File file, String hashcode) async {
final auth = context.read<AuthProvider>(); final auth = context.read<AuthProvider>();
final req = MultipartRequest( final req = MultipartRequest('POST', getRequestUri(widget.provider, '/api/attachments'));
'POST', getRequestUri(widget.provider, '/api/attachments'));
req.files.add(await MultipartFile.fromPath('attachment', file.path)); req.files.add(await MultipartFile.fromPath('attachment', file.path));
req.fields['hashcode'] = hashcode; req.fields['hashcode'] = hashcode;
@ -121,12 +154,10 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
} }
} }
Future<void> disposeAttachment( Future<void> disposeAttachment(BuildContext context, Attachment item, int index) async {
BuildContext context, Attachment item, int index) async {
final auth = context.read<AuthProvider>(); final auth = context.read<AuthProvider>();
final req = MultipartRequest('DELETE', final req = MultipartRequest('DELETE', getRequestUri(widget.provider, '/api/attachments/${item.id}'));
getRequestUri(widget.provider, '/api/attachments/${item.id}'));
setState(() => _isSubmitting = true); setState(() => _isSubmitting = true);
var res = await auth.client!.send(req); var res = await auth.client!.send(req);
@ -167,17 +198,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
if (bytes == 0) return '0 Bytes'; if (bytes == 0) return '0 Bytes';
const k = 1024; const k = 1024;
final dm = decimals < 0 ? 0 : decimals; final dm = decimals < 0 ? 0 : decimals;
final sizes = [ final sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
'Bytes',
'KiB',
'MiB',
'GiB',
'TiB',
'PiB',
'EiB',
'ZiB',
'YiB'
];
final i = (math.log(bytes) / math.log(k)).floor().toInt(); final i = (math.log(bytes) / math.log(k)).floor().toInt();
return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}'; return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
} }
@ -195,8 +216,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
return Column( return Column(
children: [ children: [
Container( Container(
padding: padding: const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12),
const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -215,9 +235,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data == true) { if (snapshot.hasData && snapshot.data == true) {
return TextButton( return TextButton(
onPressed: _isSubmitting onPressed: _isSubmitting ? null : () => viewAttachMethods(context),
? null
: () => viewAttachMethods(context),
style: TextButton.styleFrom(shape: const CircleBorder()), style: TextButton.styleFrom(shape: const CircleBorder()),
child: const Icon(Icons.add_circle), child: const Icon(Icons.add_circle),
); );
@ -229,16 +247,14 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
], ],
), ),
), ),
_isSubmitting _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
? const LinearProgressIndicator().animate().scaleX()
: Container(),
Expanded( Expanded(
child: ListView.separated( child: ListView.builder(
itemCount: _attachments.length, itemCount: _attachments.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
var element = _attachments[index]; var element = _attachments[index];
return Container( return Container(
padding: const EdgeInsets.only(left: 16, right: 8), padding: const EdgeInsets.only(left: 16, right: 8, bottom: 16),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@ -263,14 +279,12 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
foregroundColor: Colors.red, foregroundColor: Colors.red,
), ),
child: const Icon(Icons.delete), child: const Icon(Icons.delete),
onPressed: () => onPressed: () => disposeAttachment(context, element, index),
disposeAttachment(context, element, index),
), ),
], ],
), ),
); );
}, },
separatorBuilder: (context, index) => const Divider(thickness: 0.3),
), ),
), ),
], ],
@ -279,16 +293,16 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
} }
class AttachmentEditorMethodPopup extends StatelessWidget { class AttachmentEditorMethodPopup extends StatelessWidget {
final Function pickImage; final Function pickMedia;
final Function pickFile;
final Function takeImage; final Function takeImage;
final Function pickVideo;
final Function takeVideo; final Function takeVideo;
const AttachmentEditorMethodPopup({ const AttachmentEditorMethodPopup({
super.key, super.key,
required this.pickImage, required this.pickMedia,
required this.pickFile,
required this.takeImage, required this.takeImage,
required this.pickVideo,
required this.takeVideo, required this.takeVideo,
}); });
@ -319,15 +333,28 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
children: [ children: [
InkWell( InkWell(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
onTap: () => pickImage(), onTap: () => pickMedia(),
child: Center( child: Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.add_photo_alternate, const Icon(Icons.photo_library, color: Colors.indigo),
color: Colors.indigo),
const SizedBox(height: 8), const SizedBox(height: 8),
Text(AppLocalizations.of(context)!.pickPhoto), Text(AppLocalizations.of(context)!.pickMedia),
],
),
),
),
InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () => pickFile(),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.file_present, color: Colors.amber),
const SizedBox(height: 8),
Text(AppLocalizations.of(context)!.pickFile),
], ],
), ),
), ),
@ -339,27 +366,13 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.camera_alt, color: Colors.indigo), const Icon(Icons.camera_alt, color: Colors.teal),
const SizedBox(height: 8), const SizedBox(height: 8),
Text(AppLocalizations.of(context)!.takePhoto), Text(AppLocalizations.of(context)!.takePhoto),
], ],
), ),
), ),
), ),
InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () => pickVideo(),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.camera, color: Colors.teal),
const SizedBox(height: 8),
Text(AppLocalizations.of(context)!.pickVideo),
],
),
),
),
InkWell( InkWell(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
onTap: () => takeVideo(), onTap: () => takeVideo(),
@ -367,7 +380,7 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.video_call, color: Colors.teal), const Icon(Icons.movie, color: Colors.blue),
const SizedBox(height: 8), const SizedBox(height: 8),
Text(AppLocalizations.of(context)!.takeVideo), Text(AppLocalizations.of(context)!.takeVideo),
], ],

View File

@ -201,6 +201,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.0"
file_picker:
dependency: "direct main"
description:
name: file_picker
sha256: "29c90806ac5f5fb896547720b73b17ee9aed9bba540dc5d91fe29f8c5745b10a"
url: "https://pub.dev"
source: hosted
version: "8.0.3"
file_selector_linux: file_selector_linux:
dependency: transitive dependency: transitive
description: description:

View File

@ -67,6 +67,7 @@ dependencies:
wakelock_plus: ^1.2.4 wakelock_plus: ^1.2.4
flutter_local_notifications: ^17.1.0 flutter_local_notifications: ^17.1.0
draggable_float_widget: ^0.1.0 draggable_float_widget: ^0.1.0
file_picker: ^8.0.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: