diff --git a/lib/i18n/app_en.arb b/lib/i18n/app_en.arb index 85d3e3a..c64b6cc 100644 --- a/lib/i18n/app_en.arb +++ b/lib/i18n/app_en.arb @@ -23,6 +23,9 @@ "attachment": "Attachment", "attachmentAdd": "Add new attachment", "pickPhoto": "Gallery photo", + "takePhoto": "Capture photo", + "pickVideo": "Gallery video", + "takeVideo": "Record video", "newMoment": "Record a moment", "newComment": "Leave a comment", "postIdentityNotify": "You will create this post as", diff --git a/lib/i18n/app_zh.arb b/lib/i18n/app_zh.arb index 81e58d9..0a260be 100644 --- a/lib/i18n/app_zh.arb +++ b/lib/i18n/app_zh.arb @@ -23,6 +23,9 @@ "attachment": "附件", "attachmentAdd": "附加新附件", "pickPhoto": "相册照片", + "takePhoto": "拍摄照片", + "pickVideo": "相册视频", + "takeVideo": "拍摄视频", "newMoment": "记录时刻", "newComment": "留下评论", "postIdentityNotify": "你将会以该身份发表本帖子", diff --git a/lib/widgets/posts/attachment_editor.dart b/lib/widgets/posts/attachment_editor.dart index 8472db0..60402fe 100755 --- a/lib/widgets/posts/attachment_editor.dart +++ b/lib/widgets/posts/attachment_editor.dart @@ -16,8 +16,7 @@ class AttachmentEditor extends StatefulWidget { final List current; final void Function(List data) onUpdate; - const AttachmentEditor( - {super.key, required this.current, required this.onUpdate}); + const AttachmentEditor({super.key, required this.current, required this.onUpdate}); @override State createState() => _AttachmentEditorState(); @@ -34,16 +33,46 @@ class _AttachmentEditorState extends State { showModalBottomSheet( context: context, builder: (context) => AttachmentEditorMethodPopup( - pickImage: () => pickImageToUpload(context), + pickImage: () => pickImageToUpload(context, ImageSource.gallery), + takeImage: () => pickImageToUpload(context, ImageSource.camera), + pickVideo: () => pickVideoToUpload(context, ImageSource.gallery), + takeVideo: () => pickVideoToUpload(context, ImageSource.camera), ), ); } - Future pickImageToUpload(BuildContext context) async { + Future pickImageToUpload(BuildContext context, ImageSource source) async { final auth = context.read(); if (!await auth.isAuthorized()) return; - final image = await _imagePicker.pickImage(source: ImageSource.gallery); + final image = await _imagePicker.pickImage(source: source); + if (image == null) return; + + setState(() => _isSubmitting = true); + + final file = File(image.path); + final hashcode = await calculateSha256(file); + + if (Navigator.canPop(context)) { + Navigator.pop(context); + } + + try { + await uploadAttachment(file, hashcode); + } catch (err) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Something went wrong... $err")), + ); + } finally { + setState(() => _isSubmitting = false); + } + } + + Future pickVideoToUpload(BuildContext context, ImageSource source) async { + final auth = context.read(); + if (!await auth.isAuthorized()) return; + + final image = await _imagePicker.pickVideo(source: source); if (image == null) return; setState(() => _isSubmitting = true); @@ -87,8 +116,7 @@ class _AttachmentEditorState extends State { } } - Future disposeAttachment( - BuildContext context, Attachment item, int index) async { + Future disposeAttachment(BuildContext context, Attachment item, int index) async { final auth = context.read(); final req = MultipartRequest( @@ -137,17 +165,7 @@ class _AttachmentEditorState extends State { if (bytes == 0) return '0 Bytes'; const k = 1024; final dm = decimals < 0 ? 0 : decimals; - final sizes = [ - 'Bytes', - 'KiB', - 'MiB', - 'GiB', - 'TiB', - 'PiB', - 'EiB', - 'ZiB', - 'YiB' - ]; + final sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; final i = (math.log(bytes) / math.log(k)).floor().toInt(); return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}'; } @@ -184,9 +202,7 @@ class _AttachmentEditorState extends State { builder: (context, snapshot) { if (snapshot.hasData && snapshot.data == true) { return TextButton( - onPressed: _isSubmitting - ? null - : () => viewAttachMethods(context), + onPressed: _isSubmitting ? null : () => viewAttachMethods(context), style: TextButton.styleFrom(shape: const CircleBorder()), child: const Icon(Icons.add_circle), ); @@ -230,8 +246,7 @@ class _AttachmentEditorState extends State { foregroundColor: Colors.red, ), child: const Icon(Icons.delete), - onPressed: () => - disposeAttachment(context, element, index), + onPressed: () => disposeAttachment(context, element, index), ), ], ), @@ -247,8 +262,17 @@ class _AttachmentEditorState extends State { class AttachmentEditorMethodPopup extends StatelessWidget { final Function pickImage; + final Function takeImage; + final Function pickVideo; + final Function takeVideo; - const AttachmentEditorMethodPopup({super.key, required this.pickImage}); + const AttachmentEditorMethodPopup({ + super.key, + required this.pickImage, + required this.takeImage, + required this.pickVideo, + required this.takeVideo, + }); @override Widget build(BuildContext context) { @@ -280,14 +304,58 @@ class AttachmentEditorMethodPopup extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.add_photo_alternate, - color: Colors.indigo), + const Icon(Icons.add_photo_alternate, color: Colors.indigo), const SizedBox(height: 8), Text(AppLocalizations.of(context)!.pickPhoto), ], ), ), ), + InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => takeImage(), + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.camera_alt, color: Colors.indigo), + const SizedBox(height: 8), + Text(AppLocalizations.of(context)!.takePhoto), + ], + ), + ), + ), + InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => pickVideo(), + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.camera, color: Colors.indigo), + const SizedBox(height: 8), + Text(AppLocalizations.of(context)!.pickVideo), + ], + ), + ), + ), + InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => takeVideo(), + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.video_call, color: Colors.indigo), + const SizedBox(height: 8), + Text(AppLocalizations.of(context)!.takeVideo), + ], + ), + ), + ), ], ), ),