diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index f31819eb..1071754c 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -2,11 +2,11 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:image_picker/image_picker.dart'; import '../models/documents/attribute.dart'; import 'controller.dart'; import 'toolbar/arrow_indicated_button_list.dart'; +import 'toolbar/camera_button.dart'; import 'toolbar/clear_format_button.dart'; import 'toolbar/color_button.dart'; import 'toolbar/history_button.dart'; @@ -82,6 +82,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { OnVideoPickCallback? onVideoPickCallback, FilePickImpl? filePickImpl, WebImagePickImpl? webImagePickImpl, + WebVideoPickImpl? webVideoPickImpl, Key? key, }) { final isButtonGroupShown = [ @@ -173,7 +174,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { icon: Icons.image, iconSize: toolbarIconSize, controller: controller, - imageSource: ImageSource.gallery, onImagePickCallback: onImagePickCallback, filePickImpl: filePickImpl, webImagePickImpl: webImagePickImpl, @@ -183,21 +183,21 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { icon: Icons.movie_creation, iconSize: toolbarIconSize, controller: controller, - videoSource: ImageSource.gallery, onVideoPickCallback: onVideoPickCallback, filePickImpl: filePickImpl, webVideoPickImpl: webImagePickImpl, ), - if (onImagePickCallback != null && showCamera) - ImageButton( - icon: Icons.photo_camera, - iconSize: toolbarIconSize, - controller: controller, - imageSource: ImageSource.camera, - onImagePickCallback: onImagePickCallback, - filePickImpl: filePickImpl, - webImagePickImpl: webImagePickImpl, - ), + if ((onImagePickCallback != null || onVideoPickCallback != null) && + showCamera) + CameraButton( + icon: Icons.photo_camera, + iconSize: toolbarIconSize, + controller: controller, + onImagePickCallback: onImagePickCallback, + onVideoPickCallback: onVideoPickCallback, + filePickImpl: filePickImpl, + webImagePickImpl: webImagePickImpl, + webVideoPickImpl: webVideoPickImpl), if (isButtonGroupShown[0] && (isButtonGroupShown[1] || isButtonGroupShown[2] || diff --git a/lib/src/widgets/toolbar/camera_button.dart b/lib/src/widgets/toolbar/camera_button.dart new file mode 100644 index 00000000..099700f3 --- /dev/null +++ b/lib/src/widgets/toolbar/camera_button.dart @@ -0,0 +1,80 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; + +import '../controller.dart'; +import '../toolbar.dart'; +import 'image_video_utils.dart'; +import 'quill_icon_button.dart'; + +class CameraButton extends StatelessWidget { + const CameraButton({ + required this.icon, + required this.controller, + this.iconSize = kDefaultIconSize, + this.fillColor, + this.onImagePickCallback, + this.onVideoPickCallback, + this.filePickImpl, + this.webImagePickImpl, + this.webVideoPickImpl, + Key? key, + }) : super(key: key); + + final IconData icon; + final double iconSize; + + final Color? fillColor; + + final QuillController controller; + + final OnImagePickCallback? onImagePickCallback; + + final OnVideoPickCallback? onVideoPickCallback; + + final WebImagePickImpl? webImagePickImpl; + + final WebVideoPickImpl? webVideoPickImpl; + + final FilePickImpl? filePickImpl; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return QuillIconButton( + icon: Icon(icon, size: iconSize, color: theme.iconTheme.color), + highlightElevation: 0, + hoverElevation: 0, + size: iconSize * 1.77, + fillColor: fillColor ?? theme.canvasColor, + onPressed: () => _handleCameraButtonTap(context, controller, + onImagePickCallback: onImagePickCallback, + onVideoPickCallback: onVideoPickCallback, + filePickImpl: filePickImpl, + webImagePickImpl: webImagePickImpl), + ); + } + + Future _handleCameraButtonTap( + BuildContext context, QuillController controller, + {OnImagePickCallback? onImagePickCallback, + OnVideoPickCallback? onVideoPickCallback, + FilePickImpl? filePickImpl, + WebImagePickImpl? webImagePickImpl}) async { + if (onImagePickCallback != null && onVideoPickCallback != null) { + // TODO: show dialog to choose Image or Video + } + + if (onImagePickCallback != null) { + return ImageVideoUtils.handleImageButtonTap( + context, controller, ImageSource.camera, onImagePickCallback, + filePickImpl: filePickImpl, webImagePickImpl: webImagePickImpl); + } + + assert(onVideoPickCallback != null, 'onVideoPickCallback must not be null'); + return ImageVideoUtils.handleVideoButtonTap( + context, controller, ImageSource.camera, onVideoPickCallback!, + filePickImpl: filePickImpl, webVideoPickImpl: webVideoPickImpl); + } +} diff --git a/lib/src/widgets/toolbar/image_button.dart b/lib/src/widgets/toolbar/image_button.dart index 029ad83e..f5aaac51 100644 --- a/lib/src/widgets/toolbar/image_button.dart +++ b/lib/src/widgets/toolbar/image_button.dart @@ -1,22 +1,19 @@ -import 'dart:io'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; -import '../../models/documents/nodes/embed.dart'; import '../controller.dart'; import '../toolbar.dart'; +import 'image_video_utils.dart'; import 'quill_icon_button.dart'; class ImageButton extends StatelessWidget { const ImageButton({ required this.icon, required this.controller, - required this.imageSource, + required this.onImagePickCallback, this.iconSize = kDefaultIconSize, this.fillColor, - this.onImagePickCallback, this.filePickImpl, this.webImagePickImpl, Key? key, @@ -29,12 +26,10 @@ class ImageButton extends StatelessWidget { final QuillController controller; - final OnImagePickCallback? onImagePickCallback; + final OnImagePickCallback onImagePickCallback; final WebImagePickImpl? webImagePickImpl; - final ImageSource imageSource; - final FilePickImpl? filePickImpl; @override @@ -47,53 +42,9 @@ class ImageButton extends StatelessWidget { hoverElevation: 0, size: iconSize * 1.77, fillColor: fillColor ?? theme.canvasColor, - onPressed: () => _handleImageButtonTap(context, filePickImpl), + onPressed: () => ImageVideoUtils.handleImageButtonTap( + context, controller, ImageSource.gallery, onImagePickCallback, + filePickImpl: filePickImpl, webImagePickImpl: webImagePickImpl), ); } - - Future _handleImageButtonTap(BuildContext context, - [FilePickImpl? filePickImpl]) async { - final index = controller.selection.baseOffset; - final length = controller.selection.extentOffset - index; - - String? imageUrl; - if (kIsWeb) { - assert( - webImagePickImpl != null, - 'Please provide webImagePickImpl for Web ' - '(check out example directory for how to do it)'); - imageUrl = await webImagePickImpl!(onImagePickCallback!); - } else if (Platform.isAndroid || Platform.isIOS) { - imageUrl = await _pickImage(imageSource, onImagePickCallback!); - } else { - assert(filePickImpl != null, 'Desktop must provide filePickImpl'); - imageUrl = - await _pickImageDesktop(context, filePickImpl!, onImagePickCallback!); - } - - if (imageUrl != null) { - controller.replaceText(index, length, BlockEmbed.image(imageUrl), null); - } - } - - Future _pickImage( - ImageSource source, OnImagePickCallback onImagePickCallback) async { - final pickedFile = await ImagePicker().pickImage(source: source); - if (pickedFile == null) { - return null; - } - - return onImagePickCallback(File(pickedFile.path)); - } - - Future _pickImageDesktop( - BuildContext context, - FilePickImpl filePickImpl, - OnImagePickCallback onImagePickCallback) async { - final filePath = await filePickImpl(context); - if (filePath == null || filePath.isEmpty) return null; - - final file = File(filePath); - return onImagePickCallback(file); - } } diff --git a/lib/src/widgets/toolbar/image_video_utils.dart b/lib/src/widgets/toolbar/image_video_utils.dart new file mode 100644 index 00000000..6f044d0d --- /dev/null +++ b/lib/src/widgets/toolbar/image_video_utils.dart @@ -0,0 +1,115 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; + +import '../../models/documents/nodes/embed.dart'; +import '../controller.dart'; +import '../toolbar.dart'; + +class ImageVideoUtils { + /// For image picking logic + static Future handleImageButtonTap( + BuildContext context, + QuillController controller, + ImageSource imageSource, + OnImagePickCallback onImagePickCallback, + {FilePickImpl? filePickImpl, + WebImagePickImpl? webImagePickImpl}) async { + final index = controller.selection.baseOffset; + final length = controller.selection.extentOffset - index; + + String? imageUrl; + if (kIsWeb) { + assert( + webImagePickImpl != null, + 'Please provide webImagePickImpl for Web ' + '(check out example directory for how to do it)'); + imageUrl = await webImagePickImpl!(onImagePickCallback); + } else if (Platform.isAndroid || Platform.isIOS) { + imageUrl = await _pickImage(imageSource, onImagePickCallback); + } else { + assert(filePickImpl != null, 'Desktop must provide filePickImpl'); + imageUrl = + await _pickImageDesktop(context, filePickImpl!, onImagePickCallback); + } + + if (imageUrl != null) { + controller.replaceText(index, length, BlockEmbed.image(imageUrl), null); + } + } + + static Future _pickImage( + ImageSource source, OnImagePickCallback onImagePickCallback) async { + final pickedFile = await ImagePicker().pickImage(source: source); + if (pickedFile == null) { + return null; + } + + return onImagePickCallback(File(pickedFile.path)); + } + + static Future _pickImageDesktop( + BuildContext context, + FilePickImpl filePickImpl, + OnImagePickCallback onImagePickCallback) async { + final filePath = await filePickImpl(context); + if (filePath == null || filePath.isEmpty) return null; + + final file = File(filePath); + return onImagePickCallback(file); + } + + /// For video picking logic + static Future handleVideoButtonTap( + BuildContext context, + QuillController controller, + ImageSource videoSource, + OnVideoPickCallback onVideoPickCallback, + {FilePickImpl? filePickImpl, + WebVideoPickImpl? webVideoPickImpl}) async { + final index = controller.selection.baseOffset; + final length = controller.selection.extentOffset - index; + + String? videoUrl; + if (kIsWeb) { + assert( + webVideoPickImpl != null, + 'Please provide webVideoPickImpl for Web ' + '(check out example directory for how to do it)'); + videoUrl = await webVideoPickImpl!(onVideoPickCallback); + } else if (Platform.isAndroid || Platform.isIOS) { + videoUrl = await _pickVideo(ImageSource.gallery, onVideoPickCallback); + } else { + assert(filePickImpl != null, 'Desktop must provide filePickImpl'); + videoUrl = + await _pickVideoDesktop(context, filePickImpl!, onVideoPickCallback); + } + + if (videoUrl != null) { + controller.replaceText(index, length, BlockEmbed.video(videoUrl), null); + } + } + + static Future _pickVideo( + ImageSource source, OnVideoPickCallback onVideoPickCallback) async { + final pickedFile = await ImagePicker().pickVideo(source: source); + if (pickedFile == null) { + return null; + } + + return onVideoPickCallback(File(pickedFile.path)); + } + + static Future _pickVideoDesktop( + BuildContext context, + FilePickImpl filePickImpl, + OnVideoPickCallback onVideoPickCallback) async { + final filePath = await filePickImpl(context); + if (filePath == null || filePath.isEmpty) return null; + + final file = File(filePath); + return onVideoPickCallback(file); + } +} diff --git a/lib/src/widgets/toolbar/video_button.dart b/lib/src/widgets/toolbar/video_button.dart index 54f48a9c..4657dcdc 100644 --- a/lib/src/widgets/toolbar/video_button.dart +++ b/lib/src/widgets/toolbar/video_button.dart @@ -1,22 +1,19 @@ -import 'dart:io'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; -import '../../models/documents/nodes/embed.dart'; import '../controller.dart'; import '../toolbar.dart'; +import 'image_video_utils.dart'; import 'quill_icon_button.dart'; class VideoButton extends StatelessWidget { const VideoButton({ required this.icon, required this.controller, - required this.videoSource, + required this.onVideoPickCallback, this.iconSize = kDefaultIconSize, this.fillColor, - this.onVideoPickCallback, this.filePickImpl, this.webVideoPickImpl, Key? key, @@ -29,12 +26,10 @@ class VideoButton extends StatelessWidget { final QuillController controller; - final OnVideoPickCallback? onVideoPickCallback; + final OnVideoPickCallback onVideoPickCallback; final WebVideoPickImpl? webVideoPickImpl; - final ImageSource videoSource; - final FilePickImpl? filePickImpl; @override @@ -47,53 +42,9 @@ class VideoButton extends StatelessWidget { hoverElevation: 0, size: iconSize * 1.77, fillColor: fillColor ?? theme.canvasColor, - onPressed: () => _handleVideoButtonTap(context, filePickImpl), + onPressed: () => ImageVideoUtils.handleVideoButtonTap( + context, controller, ImageSource.gallery, onVideoPickCallback, + filePickImpl: filePickImpl, webVideoPickImpl: webVideoPickImpl), ); } - - Future _handleVideoButtonTap(BuildContext context, - [FilePickImpl? filePickImpl]) async { - final index = controller.selection.baseOffset; - final length = controller.selection.extentOffset - index; - - String? videoUrl; - if (kIsWeb) { - assert( - webVideoPickImpl != null, - 'Please provide webVideoPickImpl for Web ' - '(check out example directory for how to do it)'); - videoUrl = await webVideoPickImpl!(onVideoPickCallback!); - } else if (Platform.isAndroid || Platform.isIOS) { - videoUrl = await _pickVideo(videoSource, onVideoPickCallback!); - } else { - assert(filePickImpl != null, 'Desktop must provide filePickImpl'); - videoUrl = - await _pickVideoDesktop(context, filePickImpl!, onVideoPickCallback!); - } - - if (videoUrl != null) { - controller.replaceText(index, length, BlockEmbed.video(videoUrl), null); - } - } - - Future _pickVideo( - ImageSource source, OnVideoPickCallback onVideoPickCallback) async { - final pickedFile = await ImagePicker().pickVideo(source: source); - if (pickedFile == null) { - return null; - } - - return onVideoPickCallback(File(pickedFile.path)); - } - - Future _pickVideoDesktop( - BuildContext context, - FilePickImpl filePickImpl, - OnVideoPickCallback onVideoPickCallback) async { - final filePath = await filePickImpl(context); - if (filePath == null || filePath.isEmpty) return null; - - final file = File(filePath); - return onVideoPickCallback(file); - } }