@@ -2,6 +2,7 @@ import 'package:app_settings/app_settings.dart';
22import 'package:file_picker/file_picker.dart' ;
33import 'package:flutter/material.dart' ;
44import 'package:flutter/services.dart' ;
5+ import 'package:image_picker/image_picker.dart' ;
56import 'dialog.dart' ;
67
78import '../api/route/messages.dart' ;
@@ -385,6 +386,51 @@ class _AttachMediaButton extends _AttachUploadsButton {
385386 }
386387}
387388
389+ class _AttachFromCameraButton extends _AttachUploadsButton {
390+ const _AttachFromCameraButton ({required super .contentController, required super .contentFocusNode});
391+
392+ @override
393+ IconData get icon => Icons .camera_alt;
394+
395+ @override
396+ Future <Iterable <_File >> getFiles (BuildContext context) async {
397+ final picker = ImagePicker ();
398+ final XFile ? result;
399+ try {
400+ // Ideally we'd open a platform interface that lets you choose between
401+ // taking a photo and a video. `image_picker` doesn't yet have that
402+ // option: https://github.com/flutter/flutter/issues/89159
403+ // so just stick with images for now. We could add another button for
404+ // videos, but we don't want too many buttons.
405+ result = await picker.pickImage (source: ImageSource .camera, requestFullMetadata: false );
406+ } catch (e) {
407+ if (e is PlatformException && e.code == 'camera_access_denied' ) {
408+ // iOS has a quirk where it will only request the native
409+ // permission-request alert once, the first time the app wants to
410+ // use a protected resource. After that, the only way the user can
411+ // grant it is in Settings.
412+ showSuggestedActionDialog (context: context, // TODO(i18n)
413+ title: 'Permissions needed' ,
414+ message: 'To upload an image, please grant Zulip additional permissions in Settings.' ,
415+ actionButtonText: 'Open settings' ,
416+ onActionButtonPress: () {
417+ AppSettings .openAppSettings ();
418+ });
419+ } else {
420+ // TODO(i18n)
421+ showErrorDialog (context: context, title: 'Error' , message: e.toString ());
422+ }
423+ return [];
424+ }
425+ if (result == null ) {
426+ return []; // User cancelled; do nothing
427+ }
428+ final length = await result.length ();
429+
430+ return [_File (content: result.openRead (), length: length, filename: result.name)];
431+ }
432+ }
433+
388434/// The send button for StreamComposeBox.
389435class _StreamSendButton extends StatefulWidget {
390436 const _StreamSendButton ({required this .topicController, required this .contentController});
@@ -584,6 +630,7 @@ class _StreamComposeBoxState extends State<StreamComposeBox> {
584630 children: [
585631 _AttachFileButton (contentController: _contentController, contentFocusNode: _contentFocusNode),
586632 _AttachMediaButton (contentController: _contentController, contentFocusNode: _contentFocusNode),
633+ _AttachFromCameraButton (contentController: _contentController, contentFocusNode: _contentFocusNode),
587634 ])),
588635 ]))));
589636 }
0 commit comments