Add custom toolbar for photo and video. (#906)

pull/920/head
Buddypia 3 years ago committed by GitHub
parent 91eb56ae4a
commit f0ef795c84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      example/lib/pages/home_page.dart
  2. 8
      lib/src/widgets/toolbar.dart
  3. 72
      lib/src/widgets/toolbar/camera_button.dart
  4. 155
      lib/src/widgets/toolbar/camera_video_utils.dart

@ -156,6 +156,8 @@ class _HomePageState extends State<HomePage> {
onVideoPickCallback: _onVideoPickCallback, onVideoPickCallback: _onVideoPickCallback,
// uncomment to provide a custom "pick from" dialog. // uncomment to provide a custom "pick from" dialog.
// mediaPickSettingSelector: _selectMediaPickSetting, // mediaPickSettingSelector: _selectMediaPickSetting,
// uncomment to provide a custom "pick from" dialog.
// cameraPickSettingSelector: _selectCameraPickSetting,
showAlignmentButtons: true, showAlignmentButtons: true,
); );
if (kIsWeb) { if (kIsWeb) {
@ -271,6 +273,32 @@ class _HomePageState extends State<HomePage> {
), ),
); );
Future<CameraPickSetting?> _selectCameraPickSetting(BuildContext context) =>
showDialog<CameraPickSetting>(
context: context,
builder: (ctx) =>
AlertDialog(
contentPadding: EdgeInsets.zero,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton.icon(
icon: const Icon(Icons.camera),
label: const Text('Capture a photo'),
onPressed: () =>
Navigator.pop(ctx, CameraPickSetting.Camera),
),
TextButton.icon(
icon: const Icon(Icons.video_call),
label: const Text('Capture a video'),
onPressed: () =>
Navigator.pop(ctx, CameraPickSetting.Video),
)
],
),
),
);
Widget _buildMenuBar(BuildContext context) { Widget _buildMenuBar(BuildContext context) {
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
const itemStyle = TextStyle( const itemStyle = TextStyle(
@ -400,4 +428,4 @@ class NotesBlockEmbed extends CustomBlockEmbed {
NotesBlockEmbed(jsonEncode(document.toDelta().toJson())); NotesBlockEmbed(jsonEncode(document.toDelta().toJson()));
Document get document => Document.fromJson(jsonDecode(data)); Document get document => Document.fromJson(jsonDecode(data));
} }

@ -18,6 +18,7 @@ import 'toolbar/formula_button.dart';
import 'toolbar/history_button.dart'; import 'toolbar/history_button.dart';
import 'toolbar/image_button.dart'; import 'toolbar/image_button.dart';
import 'toolbar/image_video_utils.dart'; import 'toolbar/image_video_utils.dart';
import 'toolbar/camera_video_utils.dart';
import 'toolbar/indent_button.dart'; import 'toolbar/indent_button.dart';
import 'toolbar/link_style_button.dart'; import 'toolbar/link_style_button.dart';
import 'toolbar/quill_font_family_button.dart'; import 'toolbar/quill_font_family_button.dart';
@ -35,6 +36,7 @@ export 'toolbar/color_button.dart';
export 'toolbar/history_button.dart'; export 'toolbar/history_button.dart';
export 'toolbar/image_button.dart'; export 'toolbar/image_button.dart';
export 'toolbar/image_video_utils.dart'; export 'toolbar/image_video_utils.dart';
export 'toolbar/camera_video_utils.dart';
export 'toolbar/indent_button.dart'; export 'toolbar/indent_button.dart';
export 'toolbar/link_style_button.dart'; export 'toolbar/link_style_button.dart';
export 'toolbar/quill_font_size_button.dart'; export 'toolbar/quill_font_size_button.dart';
@ -54,6 +56,8 @@ typedef WebVideoPickImpl = Future<String?> Function(
OnVideoPickCallback onImagePickCallback); OnVideoPickCallback onImagePickCallback);
typedef MediaPickSettingSelector = Future<MediaPickSetting?> Function( typedef MediaPickSettingSelector = Future<MediaPickSetting?> Function(
BuildContext context); BuildContext context);
typedef CameraPickSettingSelector = Future<CameraPickSetting?> Function(
BuildContext context);
// The default size of the icon of a button. // The default size of the icon of a button.
const double kDefaultIconSize = 18; const double kDefaultIconSize = 18;
@ -117,6 +121,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
OnImagePickCallback? onImagePickCallback, OnImagePickCallback? onImagePickCallback,
OnVideoPickCallback? onVideoPickCallback, OnVideoPickCallback? onVideoPickCallback,
MediaPickSettingSelector? mediaPickSettingSelector, MediaPickSettingSelector? mediaPickSettingSelector,
CameraPickSettingSelector? cameraPickSettingSelector,
FilePickImpl? filePickImpl, FilePickImpl? filePickImpl,
WebImagePickImpl? webImagePickImpl, WebImagePickImpl? webImagePickImpl,
WebVideoPickImpl? webVideoPickImpl, WebVideoPickImpl? webVideoPickImpl,
@ -364,6 +369,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
filePickImpl: filePickImpl, filePickImpl: filePickImpl,
webImagePickImpl: webImagePickImpl, webImagePickImpl: webImagePickImpl,
webVideoPickImpl: webVideoPickImpl, webVideoPickImpl: webVideoPickImpl,
cameraPickSettingSelector: cameraPickSettingSelector,
iconTheme: iconTheme, iconTheme: iconTheme,
), ),
if (showFormulaButton) if (showFormulaButton)
@ -583,4 +589,4 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
), ),
); );
} }
} }

@ -16,6 +16,7 @@ class CameraButton extends StatelessWidget {
this.filePickImpl, this.filePickImpl,
this.webImagePickImpl, this.webImagePickImpl,
this.webVideoPickImpl, this.webVideoPickImpl,
this.cameraPickSettingSelector,
this.iconTheme, this.iconTheme,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -37,6 +38,8 @@ class CameraButton extends StatelessWidget {
final FilePickImpl? filePickImpl; final FilePickImpl? filePickImpl;
final CameraPickSettingSelector? cameraPickSettingSelector;
final QuillIconTheme? iconTheme; final QuillIconTheme? iconTheme;
@override @override
@ -54,7 +57,7 @@ class CameraButton extends StatelessWidget {
size: iconSize * 1.77, size: iconSize * 1.77,
fillColor: iconFillColor, fillColor: iconFillColor,
borderRadius: iconTheme?.borderRadius ?? 2, borderRadius: iconTheme?.borderRadius ?? 2,
onPressed: () => _handleCameraButtonTap(context, controller, onPressed: () => _onPressedHandler(context, controller,
onImagePickCallback: onImagePickCallback, onImagePickCallback: onImagePickCallback,
onVideoPickCallback: onVideoPickCallback, onVideoPickCallback: onVideoPickCallback,
filePickImpl: filePickImpl, filePickImpl: filePickImpl,
@ -62,55 +65,36 @@ class CameraButton extends StatelessWidget {
); );
} }
Future<void> _handleCameraButtonTap( Future<void> _onPressedHandler(
BuildContext context, QuillController controller, BuildContext context, QuillController controller,
{OnImagePickCallback? onImagePickCallback, {OnImagePickCallback? onImagePickCallback,
OnVideoPickCallback? onVideoPickCallback, OnVideoPickCallback? onVideoPickCallback,
FilePickImpl? filePickImpl, FilePickImpl? filePickImpl,
WebImagePickImpl? webImagePickImpl}) async { WebImagePickImpl? webImagePickImpl}) async {
if (onImagePickCallback != null && onVideoPickCallback != null) { if (onImagePickCallback != null && onVideoPickCallback != null) {
// Show dialog to choose Photo or Video
return await showDialog(
context: context,
builder: (context) {
return AlertDialog(
contentPadding: const EdgeInsets.all(0),
backgroundColor: Colors.transparent,
content: Column(mainAxisSize: MainAxisSize.min, children: [
TextButton.icon(
icon: const Icon(Icons.photo, color: Colors.cyanAccent),
label: const Text('Photo'),
onPressed: () {
ImageVideoUtils.handleImageButtonTap(context, controller,
ImageSource.camera, onImagePickCallback,
filePickImpl: filePickImpl,
webImagePickImpl: webImagePickImpl);
},
),
TextButton.icon(
icon: const Icon(Icons.movie_creation,
color: Colors.orangeAccent),
label: const Text('Video'),
onPressed: () {
ImageVideoUtils.handleVideoButtonTap(context, controller,
ImageSource.camera, onVideoPickCallback,
filePickImpl: filePickImpl,
webVideoPickImpl: webVideoPickImpl);
},
)
]));
});
}
if (onImagePickCallback != null) { final selector =
return ImageVideoUtils.handleImageButtonTap( cameraPickSettingSelector ?? CameraVideoUtils.selectMediaPickSetting;
context, controller, ImageSource.camera, onImagePickCallback,
filePickImpl: filePickImpl, webImagePickImpl: webImagePickImpl); final source = await selector(context);
if (source != null) {
switch (source) {
case CameraPickSetting.Camera:
CameraVideoUtils.handleCameraButtonTap(context, controller,
ImageSource.camera, onImagePickCallback,
filePickImpl: filePickImpl,
webImagePickImpl: webImagePickImpl);
break;
case CameraPickSetting.Video:
CameraVideoUtils.handleVideoButtonTap(context, controller,
ImageSource.camera, onVideoPickCallback,
filePickImpl: filePickImpl,
webVideoPickImpl: webVideoPickImpl);
break;
default:
throw ArgumentError('Invalid MediaSetting');
}
}
} }
assert(onVideoPickCallback != null, 'onVideoPickCallback must not be null');
return ImageVideoUtils.handleVideoButtonTap(
context, controller, ImageSource.camera, onVideoPickCallback!,
filePickImpl: filePickImpl, webVideoPickImpl: webVideoPickImpl);
} }
} }

@ -0,0 +1,155 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import '../../models/documents/nodes/embeddable.dart';
import '../../models/rules/insert.dart';
import '../../models/themes/quill_dialog_theme.dart';
import '../../translations/toolbar.i18n.dart';
import '../../utils/platform.dart';
import '../controller.dart';
import '../toolbar.dart';
enum CameraPickSetting {
Camera,
Video,
}
class CameraVideoUtils {
static Future<CameraPickSetting?> selectMediaPickSetting(
BuildContext context,
) =>
showDialog<CameraPickSetting>(
context: context,
builder: (ctx) => AlertDialog(
contentPadding: EdgeInsets.zero,
backgroundColor: Colors.transparent,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton.icon(
icon: const Icon(
Icons.camera,
color: Colors.orangeAccent,
),
label: Text('Camera'),
onPressed: () => Navigator.pop(ctx, CameraPickSetting.Camera),
),
TextButton.icon(
icon: const Icon(
Icons.video_call,
color: Colors.cyanAccent,
),
label: Text('Video'),
onPressed: () => Navigator.pop(ctx, CameraPickSetting.Video),
)
],
),
),
);
static Future<void> handleCameraButtonTap(
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 (isMobile()) {
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<String?> _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<String?> _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<void> 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 (isMobile()) {
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);
}
}
static Future<String?> _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<String?> _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);
}
}
Loading…
Cancel
Save