parent
65d1cb6d4a
commit
1548b4b537
44 changed files with 35 additions and 5453 deletions
@ -1,277 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_quill/flutter_quill.dart' show QuillDialogTheme; |
|
||||||
import 'package:flutter_quill/translations.dart'; |
|
||||||
|
|
||||||
import '../../utils/patterns.dart'; |
|
||||||
|
|
||||||
enum LinkType { |
|
||||||
video, |
|
||||||
image, |
|
||||||
} |
|
||||||
|
|
||||||
class TypeLinkDialog extends StatefulWidget { |
|
||||||
const TypeLinkDialog({ |
|
||||||
required this.linkType, |
|
||||||
this.dialogTheme, |
|
||||||
this.link, |
|
||||||
this.linkRegExp, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final QuillDialogTheme? dialogTheme; |
|
||||||
final String? link; |
|
||||||
final RegExp? linkRegExp; |
|
||||||
final LinkType linkType; |
|
||||||
|
|
||||||
@override |
|
||||||
TypeLinkDialogState createState() => TypeLinkDialogState(); |
|
||||||
} |
|
||||||
|
|
||||||
class TypeLinkDialogState extends State<TypeLinkDialog> { |
|
||||||
late String _link; |
|
||||||
late TextEditingController _controller; |
|
||||||
RegExp? _linkRegExp; |
|
||||||
|
|
||||||
@override |
|
||||||
void initState() { |
|
||||||
super.initState(); |
|
||||||
_link = widget.link ?? ''; |
|
||||||
_controller = TextEditingController(text: _link); |
|
||||||
|
|
||||||
_linkRegExp = widget.linkRegExp; |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
_controller.dispose(); |
|
||||||
super.dispose(); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
return AlertDialog( |
|
||||||
backgroundColor: widget.dialogTheme?.dialogBackgroundColor, |
|
||||||
content: TextField( |
|
||||||
keyboardType: TextInputType.url, |
|
||||||
textInputAction: TextInputAction.done, |
|
||||||
maxLines: null, |
|
||||||
style: widget.dialogTheme?.inputTextStyle, |
|
||||||
decoration: InputDecoration( |
|
||||||
labelText: context.loc.pasteLink, |
|
||||||
hintText: widget.linkType == LinkType.image |
|
||||||
? context.loc.pleaseEnterAValidImageURL |
|
||||||
: context.loc.pleaseEnterAValidVideoURL, |
|
||||||
labelStyle: widget.dialogTheme?.labelTextStyle, |
|
||||||
floatingLabelStyle: widget.dialogTheme?.labelTextStyle, |
|
||||||
), |
|
||||||
autofocus: true, |
|
||||||
onChanged: _linkChanged, |
|
||||||
controller: _controller, |
|
||||||
onEditingComplete: () { |
|
||||||
if (!_canPress()) { |
|
||||||
return; |
|
||||||
} |
|
||||||
_applyLink(); |
|
||||||
}, |
|
||||||
), |
|
||||||
actions: [ |
|
||||||
TextButton( |
|
||||||
onPressed: _canPress() ? _applyLink : null, |
|
||||||
child: Text( |
|
||||||
context.loc.ok, |
|
||||||
style: widget.dialogTheme?.labelTextStyle, |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
void _linkChanged(String value) { |
|
||||||
setState(() { |
|
||||||
_link = value; |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
void _applyLink() { |
|
||||||
Navigator.pop(context, _link.trim()); |
|
||||||
} |
|
||||||
|
|
||||||
RegExp get linkRegExp { |
|
||||||
final customRegExp = _linkRegExp; |
|
||||||
if (customRegExp != null) { |
|
||||||
return customRegExp; |
|
||||||
} |
|
||||||
switch (widget.linkType) { |
|
||||||
case LinkType.video: |
|
||||||
if (youtubeRegExp.hasMatch(_link)) { |
|
||||||
return youtubeRegExp; |
|
||||||
} |
|
||||||
return videoRegExp; |
|
||||||
case LinkType.image: |
|
||||||
return imageRegExp; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool _canPress() { |
|
||||||
if (_link.isEmpty) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
if (widget.linkType == LinkType.image) {} |
|
||||||
return _link.isNotEmpty && linkRegExp.hasMatch(_link); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// @immutable |
|
||||||
// class ImageVideoUtils { |
|
||||||
// const ImageVideoUtils._(); |
|
||||||
// static Future<MediaPickSetting?> selectMediaPickSetting( |
|
||||||
// BuildContext context, |
|
||||||
// ) => |
|
||||||
// showDialog<MediaPickSetting>( |
|
||||||
// context: context, |
|
||||||
// builder: (ctx) => AlertDialog( |
|
||||||
// contentPadding: EdgeInsets.zero, |
|
||||||
// content: Column( |
|
||||||
// mainAxisSize: MainAxisSize.min, |
|
||||||
// children: [ |
|
||||||
// TextButton.icon( |
|
||||||
// icon: const Icon( |
|
||||||
// Icons.collections, |
|
||||||
// color: Colors.orangeAccent, |
|
||||||
// ), |
|
||||||
// label: Text('Gallery'.i18n), |
|
||||||
// onPressed: () => Navigator.pop(ctx, |
|
||||||
// MediaPickSetting.gallery), |
|
||||||
// ), |
|
||||||
// TextButton.icon( |
|
||||||
// icon: const Icon( |
|
||||||
// Icons.link, |
|
||||||
// color: Colors.cyanAccent, |
|
||||||
// ), |
|
||||||
// label: Text('Link'.i18n), |
|
||||||
// onPressed: () => Navigator.pop(ctx, MediaPickSetting.link), |
|
||||||
// ) |
|
||||||
// ], |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ); |
|
||||||
|
|
||||||
// /// For image picking logic |
|
||||||
// static Future<void> handleImageButtonTap( |
|
||||||
// BuildContext context, |
|
||||||
// QuillController controller, |
|
||||||
// ImageSource imageSource, |
|
||||||
// OnImagePickCallback onImagePickCallback, { |
|
||||||
// FilePickImpl? filePickImpl, |
|
||||||
// WebImagePickImpl? webImagePickImpl, |
|
||||||
// }) async { |
|
||||||
// String? imageUrl; |
|
||||||
// if (kIsWeb) { |
|
||||||
// if (webImagePickImpl != null) { |
|
||||||
// imageUrl = await webImagePickImpl(onImagePickCallback); |
|
||||||
// return; |
|
||||||
// } |
|
||||||
// final file = await ImagePicker() |
|
||||||
//.pickImage(source: ImageSource.gallery); |
|
||||||
// imageUrl = file?.path; |
|
||||||
// if (imageUrl == null) { |
|
||||||
// return; |
|
||||||
// } |
|
||||||
// } 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) { |
|
||||||
// return; |
|
||||||
// } |
|
||||||
|
|
||||||
// controller.insertImageBlock( |
|
||||||
// imageUrl: imageUrl, |
|
||||||
// ); |
|
||||||
// } |
|
||||||
|
|
||||||
// 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 ' |
|
||||||
// 'in the options of this button', |
|
||||||
// ); |
|
||||||
// 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); |
|
||||||
// } |
|
||||||
// } |
|
@ -1,537 +0,0 @@ |
|||||||
// // ignore_for_file: use_build_context_synchronously |
|
||||||
|
|
||||||
// import 'dart:math' as math; |
|
||||||
// import 'dart:ui'; |
|
||||||
|
|
||||||
// import 'package:flutter/foundation.dart'; |
|
||||||
// import 'package:flutter/material.dart'; |
|
||||||
// import 'package:flutter_quill/extensions.dart'; |
|
||||||
// import 'package:flutter_quill/flutter_quill.dart'; |
|
||||||
// import 'package:flutter_quill/translations.dart'; |
|
||||||
// import 'package:image_picker/image_picker.dart'; |
|
||||||
|
|
||||||
// import '../../../models/config/toolbar/buttons/media_button.dart'; |
|
||||||
// import '../../embed_types.dart'; |
|
||||||
// import '../utils/image_video_utils.dart'; |
|
||||||
|
|
||||||
// /// Widget which combines [ImageButton] and [VideButton] widgets. This widget |
|
||||||
// /// has more customization and uses dialog similar to one which is used |
|
||||||
// /// on [http://quilljs.com]. |
|
||||||
// class QuillToolbarMediaButton extends StatelessWidget { |
|
||||||
// QuillToolbarMediaButton({ |
|
||||||
// required this.controller, |
|
||||||
// this.options, |
|
||||||
// super.key, |
|
||||||
// }) : assert(options.type == QuillMediaType.image, |
|
||||||
// 'Video selection is not supported yet'); |
|
||||||
|
|
||||||
// final QuillController controller; |
|
||||||
// final QuillToolbarMediaButtonOptions options; |
|
||||||
|
|
||||||
// double _iconSize(BuildContext context) { |
|
||||||
// final baseFontSize = baseButtonExtraOptions(context).globalIconSize; |
|
||||||
// final iconSize = options.iconSize; |
|
||||||
// return iconSize ?? baseFontSize; |
|
||||||
// } |
|
||||||
|
|
||||||
// VoidCallback? _afterButtonPressed(BuildContext context) { |
|
||||||
// return options.afterButtonPressed ?? |
|
||||||
// baseButtonExtraOptions(context).afterButtonPressed; |
|
||||||
// } |
|
||||||
|
|
||||||
// QuillIconTheme? _iconTheme(BuildContext context) { |
|
||||||
// return options.iconTheme ?? baseButtonExtraOptions(context).iconTheme; |
|
||||||
// } |
|
||||||
|
|
||||||
// QuillToolbarBaseButtonOptions baseButtonExtraOptions( |
|
||||||
//BuildContext context) { |
|
||||||
// return context.requireQuillToolbarBaseButtonOptions; |
|
||||||
// } |
|
||||||
|
|
||||||
// (IconData, String) get _defaultData { |
|
||||||
// switch (options.type) { |
|
||||||
// case QuillMediaType.image: |
|
||||||
// return (Icons.perm_media, 'Photo media button'); |
|
||||||
// case QuillMediaType.video: |
|
||||||
// throw UnsupportedError('The video is not supported yet.'); |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
// IconData _iconData(BuildContext context) { |
|
||||||
// return options.iconData ?? |
|
||||||
// baseButtonExtraOptions(context).iconData ?? |
|
||||||
// _defaultData.$1; |
|
||||||
// } |
|
||||||
|
|
||||||
// String _tooltip(BuildContext context) { |
|
||||||
// return options.tooltip ?? |
|
||||||
// baseButtonExtraOptions(context).tooltip ?? |
|
||||||
// _defaultData.$2; |
|
||||||
// // ('Camera'.i18n); |
|
||||||
// } |
|
||||||
|
|
||||||
// void _sharedOnPressed(BuildContext context) { |
|
||||||
// _onPressedHandler(context); |
|
||||||
// _afterButtonPressed(context); |
|
||||||
// } |
|
||||||
|
|
||||||
// @override |
|
||||||
// Widget build(BuildContext context) { |
|
||||||
// final tooltip = _tooltip(context); |
|
||||||
// final iconSize = _iconSize(context); |
|
||||||
// final iconData = _iconData(context); |
|
||||||
// final childBuilder = |
|
||||||
// options.childBuilder ?? baseButtonExtraOptions(context).childBuilder; |
|
||||||
// final iconTheme = _iconTheme(context); |
|
||||||
|
|
||||||
// if (childBuilder != null) { |
|
||||||
// return childBuilder( |
|
||||||
// QuillToolbarMediaButtonOptions( |
|
||||||
// type: options.type, |
|
||||||
// onMediaPickedCallback: options.onMediaPickedCallback, |
|
||||||
// onImagePickCallback: options.onImagePickCallback, |
|
||||||
// onVideoPickCallback: options.onVideoPickCallback, |
|
||||||
// iconData: iconData, |
|
||||||
// afterButtonPressed: _afterButtonPressed(context), |
|
||||||
// autovalidateMode: options.autovalidateMode, |
|
||||||
// childrenSpacing: options.childrenSpacing, |
|
||||||
// dialogBarrierColor: options.dialogBarrierColor, |
|
||||||
// dialogTheme: options.dialogTheme, |
|
||||||
// filePickImpl: options.filePickImpl, |
|
||||||
// fillColor: options.fillColor, |
|
||||||
// galleryButtonText: options.galleryButtonText, |
|
||||||
// iconTheme: iconTheme, |
|
||||||
// iconSize: iconSize, |
|
||||||
// iconButtonFactor: iconButtonFactor, |
|
||||||
// hintText: options.hintText, |
|
||||||
// labelText: options.labelText, |
|
||||||
// submitButtonSize: options.submitButtonSize, |
|
||||||
// linkButtonText: options.linkButtonText, |
|
||||||
// mediaFilePicker: options.mediaFilePicker, |
|
||||||
// submitButtonText: options.submitButtonText, |
|
||||||
// validationMessage: options.validationMessage, |
|
||||||
// webImagePickImpl: options.webImagePickImpl, |
|
||||||
// webVideoPickImpl: options.webVideoPickImpl, |
|
||||||
// tooltip: options.tooltip, |
|
||||||
// ), |
|
||||||
// QuillToolbarMediaButtonExtraOptions( |
|
||||||
// context: context, |
|
||||||
// controller: controller, |
|
||||||
// onPressed: () => _sharedOnPressed(context), |
|
||||||
// ), |
|
||||||
// ); |
|
||||||
// } |
|
||||||
|
|
||||||
// final theme = Theme.of(context); |
|
||||||
|
|
||||||
// final iconColor = |
|
||||||
// options.iconTheme?.iconUnselectedColor ?? theme.iconTheme.color; |
|
||||||
// final iconFillColor = options.iconTheme?.iconUnselectedFillColor ?? |
|
||||||
// options.fillColor ?? |
|
||||||
// theme.canvasColor; |
|
||||||
|
|
||||||
// return QuillToolbarIconButton( |
|
||||||
// icon: Icon(iconData, size: iconSize, color: iconColor), |
|
||||||
// tooltip: tooltip, |
|
||||||
// highlightElevation: 0, |
|
||||||
// hoverElevation: 0, |
|
||||||
// size: iconSize * iconButtonFactor, |
|
||||||
// fillColor: iconFillColor, |
|
||||||
// borderRadius: iconTheme?.borderRadius ?? 2, |
|
||||||
// onPressed: () => _sharedOnPressed(context), |
|
||||||
// ); |
|
||||||
// } |
|
||||||
|
|
||||||
// Future<void> _onPressedHandler(BuildContext context) async { |
|
||||||
// if (options.onMediaPickedCallback == null) { |
|
||||||
// _inputLink(context); |
|
||||||
// return; |
|
||||||
// } |
|
||||||
// final mediaSource = await showDialog<MediaPickSetting>( |
|
||||||
// context: context, |
|
||||||
// builder: (_) => MediaSourceSelectorDialog( |
|
||||||
// dialogTheme: options.dialogTheme, |
|
||||||
// galleryButtonText: options.galleryButtonText, |
|
||||||
// linkButtonText: options.linkButtonText, |
|
||||||
// ), |
|
||||||
// ); |
|
||||||
// if (mediaSource == null) { |
|
||||||
// return; |
|
||||||
// } |
|
||||||
// switch (mediaSource) { |
|
||||||
// case MediaPickSetting.gallery: |
|
||||||
// await _pickImage(); |
|
||||||
// break; |
|
||||||
// case MediaPickSetting.link: |
|
||||||
// _inputLink(context); |
|
||||||
// break; |
|
||||||
// case MediaPickSetting.camera: |
|
||||||
// await ImageVideoUtils.handleImageButtonTap( |
|
||||||
// context, |
|
||||||
// controller, |
|
||||||
// ImageSource.camera, |
|
||||||
// options.onImagePickCallback, |
|
||||||
// filePickImpl: options.filePickImpl, |
|
||||||
// webImagePickImpl: options.webImagePickImpl, |
|
||||||
// ); |
|
||||||
// break; |
|
||||||
// case MediaPickSetting.video: |
|
||||||
// await ImageVideoUtils.handleVideoButtonTap( |
|
||||||
// context, |
|
||||||
// controller, |
|
||||||
// ImageSource.camera, |
|
||||||
// options.onVideoPickCallback, |
|
||||||
// filePickImpl: options.filePickImpl, |
|
||||||
// webVideoPickImpl: options.webVideoPickImpl, |
|
||||||
// ); |
|
||||||
// break; |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
// Future<void> _pickImage() async { |
|
||||||
// if (!(kIsWeb || isMobile() || isDesktop())) { |
|
||||||
// throw UnsupportedError( |
|
||||||
// 'Unsupported target platform: ${defaultTargetPlatform.name}', |
|
||||||
// ); |
|
||||||
// } |
|
||||||
|
|
||||||
// final mediaFileUrl = await _pickMediaFileUrl(); |
|
||||||
|
|
||||||
// if (mediaFileUrl != null) { |
|
||||||
// final index = controller.selection.baseOffset; |
|
||||||
// final length = controller.selection.extentOffset - index; |
|
||||||
// controller.replaceText( |
|
||||||
// index, |
|
||||||
// length, |
|
||||||
// BlockEmbed.image(mediaFileUrl), |
|
||||||
// null, |
|
||||||
// ); |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
// Future<MediaFileUrl?> _pickMediaFileUrl() async { |
|
||||||
// final mediaFile = await options.mediaFilePicker?.call(options.type); |
|
||||||
// return mediaFile != null |
|
||||||
// ? options.onMediaPickedCallback?.call(mediaFile) |
|
||||||
// : null; |
|
||||||
// } |
|
||||||
|
|
||||||
// void _inputLink(BuildContext context) { |
|
||||||
// showDialog<String>( |
|
||||||
// context: context, |
|
||||||
// builder: (_) => MediaLinkDialog( |
|
||||||
// dialogTheme: options.dialogTheme, |
|
||||||
// labelText: options.labelText, |
|
||||||
// hintText: options.hintText, |
|
||||||
// buttonText: options.submitButtonText, |
|
||||||
// buttonSize: options.submitButtonSize, |
|
||||||
// childrenSpacing: options.childrenSpacing, |
|
||||||
// autovalidateMode: options.autovalidateMode, |
|
||||||
// validationMessage: options.validationMessage, |
|
||||||
// ), |
|
||||||
// ).then(_linkSubmitted); |
|
||||||
// } |
|
||||||
|
|
||||||
// void _linkSubmitted(String? value) { |
|
||||||
// if (value != null && value.isNotEmpty) { |
|
||||||
// final index = controller.selection.baseOffset; |
|
||||||
// final length = controller.selection.extentOffset - index; |
|
||||||
// final data = options.type.isImage |
|
||||||
// ? BlockEmbed.image(value) |
|
||||||
// : BlockEmbed.video(value); |
|
||||||
// controller.replaceText(index, length, data, null); |
|
||||||
// } |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
// /// Provides a dialog for input link to media resource. |
|
||||||
// class MediaLinkDialog extends StatefulWidget { |
|
||||||
// const MediaLinkDialog({ |
|
||||||
// super.key, |
|
||||||
// this.link, |
|
||||||
// this.dialogTheme, |
|
||||||
// this.childrenSpacing = 16.0, |
|
||||||
// this.labelText, |
|
||||||
// this.hintText, |
|
||||||
// this.buttonText, |
|
||||||
// this.buttonSize, |
|
||||||
// this.autovalidateMode = AutovalidateMode.disabled, |
|
||||||
// this.validationMessage, |
|
||||||
// }) : assert(childrenSpacing > 0); |
|
||||||
|
|
||||||
// final String? link; |
|
||||||
// final QuillDialogTheme? dialogTheme; |
|
||||||
|
|
||||||
// /// The margin between child widgets in the dialog. |
|
||||||
// final double childrenSpacing; |
|
||||||
|
|
||||||
// /// The text of label in link add mode. |
|
||||||
// final String? labelText; |
|
||||||
|
|
||||||
// /// The hint text for link [TextField]. |
|
||||||
// final String? hintText; |
|
||||||
|
|
||||||
// /// The text of the submit button. |
|
||||||
// final String? buttonText; |
|
||||||
|
|
||||||
// /// The size of dialog buttons. |
|
||||||
// final Size? buttonSize; |
|
||||||
|
|
||||||
// final AutovalidateMode autovalidateMode; |
|
||||||
// final String? validationMessage; |
|
||||||
|
|
||||||
// @override |
|
||||||
// State<MediaLinkDialog> createState() => _MediaLinkDialogState(); |
|
||||||
// } |
|
||||||
|
|
||||||
// class _MediaLinkDialogState extends State<MediaLinkDialog> { |
|
||||||
// final _linkFocus = FocusNode(); |
|
||||||
// final _linkController = TextEditingController(); |
|
||||||
|
|
||||||
// @override |
|
||||||
// void dispose() { |
|
||||||
// _linkFocus.dispose(); |
|
||||||
// _linkController.dispose(); |
|
||||||
// super.dispose(); |
|
||||||
// } |
|
||||||
|
|
||||||
// @override |
|
||||||
// Widget build(BuildContext context) { |
|
||||||
// final constraints = widget.dialogTheme?.linkDialogConstraints ?? |
|
||||||
// () { |
|
||||||
// final size = MediaQuery.sizeOf(context); |
|
||||||
// final maxWidth = kIsWeb ? size.width / 4 : size.width - 80; |
|
||||||
// return BoxConstraints(maxWidth: maxWidth, maxHeight: 80); |
|
||||||
// }(); |
|
||||||
|
|
||||||
// final buttonStyle = widget.buttonSize != null |
|
||||||
// ? Theme.of(context) |
|
||||||
// .elevatedButtonTheme |
|
||||||
// .style |
|
||||||
// ?.copyWith( |
|
||||||
//fixedSize: MaterialStatePropertyAll(widget.buttonSize)) |
|
||||||
// : widget.dialogTheme?.buttonStyle; |
|
||||||
|
|
||||||
// final isWrappable = widget.dialogTheme?.isWrappable ?? false; |
|
||||||
|
|
||||||
// final children = [ |
|
||||||
// Text(widget.labelText ?? 'Enter media'.i18n), |
|
||||||
// UtilityWidgets.maybeWidget( |
|
||||||
// enabled: !isWrappable, |
|
||||||
// wrapper: (child) => Expanded( |
|
||||||
// child: child, |
|
||||||
// ), |
|
||||||
// child: Padding( |
|
||||||
// padding: EdgeInsets.symmetric(horizontal: widget.childrenSpacing), |
|
||||||
// child: TextFormField( |
|
||||||
// controller: _linkController, |
|
||||||
// focusNode: _linkFocus, |
|
||||||
// style: widget.dialogTheme?.inputTextStyle, |
|
||||||
// keyboardType: TextInputType.url, |
|
||||||
// textInputAction: TextInputAction.done, |
|
||||||
// decoration: InputDecoration( |
|
||||||
// labelStyle: widget.dialogTheme?.labelTextStyle, |
|
||||||
// hintText: widget.hintText, |
|
||||||
// ), |
|
||||||
// autofocus: true, |
|
||||||
// autovalidateMode: widget.autovalidateMode, |
|
||||||
// validator: _validateLink, |
|
||||||
// onChanged: _linkChanged, |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ElevatedButton( |
|
||||||
// onPressed: _canPress() ? _submitLink : null, |
|
||||||
// style: buttonStyle, |
|
||||||
// child: Text(widget.buttonText ?? 'Ok'.i18n), |
|
||||||
// ), |
|
||||||
// ]; |
|
||||||
|
|
||||||
// return Dialog( |
|
||||||
// backgroundColor: widget.dialogTheme?.dialogBackgroundColor, |
|
||||||
// shape: widget.dialogTheme?.shape ?? |
|
||||||
// DialogTheme.of(context).shape ?? |
|
||||||
// RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), |
|
||||||
// child: ConstrainedBox( |
|
||||||
// constraints: constraints, |
|
||||||
// child: Padding( |
|
||||||
// padding: |
|
||||||
// widget.dialogTheme?.linkDialogPadding ?? const |
|
||||||
// EdgeInsets.all(16), |
|
||||||
// child: Form( |
|
||||||
// child: isWrappable |
|
||||||
// ? Wrap( |
|
||||||
// alignment: WrapAlignment.center, |
|
||||||
// crossAxisAlignment: WrapCrossAlignment.center, |
|
||||||
// runSpacing: widget.dialogTheme?.runSpacing ?? 0.0, |
|
||||||
// children: children, |
|
||||||
// ) |
|
||||||
// : Row( |
|
||||||
// children: children, |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ); |
|
||||||
// } |
|
||||||
|
|
||||||
// bool _canPress() => _validateLink(_linkController.text) == null; |
|
||||||
|
|
||||||
// void _linkChanged(String value) { |
|
||||||
// setState(() { |
|
||||||
// _linkController.text = value; |
|
||||||
// }); |
|
||||||
// } |
|
||||||
|
|
||||||
// void _submitLink() => Navigator.pop(context, _linkController.text); |
|
||||||
|
|
||||||
// String? _validateLink(String? value) { |
|
||||||
// if ((value?.isEmpty ?? false) || |
|
||||||
// !AutoFormatMultipleLinksRule.oneLineLinkRegExp.hasMatch(value!)) { |
|
||||||
// return widget.validationMessage ?? 'That is not a valid URL'; |
|
||||||
// } |
|
||||||
|
|
||||||
// return null; |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
// /// Media souce selector. |
|
||||||
// class MediaSourceSelectorDialog extends StatelessWidget { |
|
||||||
// const MediaSourceSelectorDialog({ |
|
||||||
// super.key, |
|
||||||
// this.dialogTheme, |
|
||||||
// this.galleryButtonText, |
|
||||||
// this.linkButtonText, |
|
||||||
// }); |
|
||||||
|
|
||||||
// final QuillDialogTheme? dialogTheme; |
|
||||||
|
|
||||||
// /// The text of the gallery button [MediaSourceSelectorDialog]. |
|
||||||
// final String? galleryButtonText; |
|
||||||
|
|
||||||
// /// The text of the link button [MediaSourceSelectorDialog]. |
|
||||||
// final String? linkButtonText; |
|
||||||
|
|
||||||
// @override |
|
||||||
// Widget build(BuildContext context) { |
|
||||||
// final constraints = dialogTheme?.mediaSelectorDialogConstraints ?? |
|
||||||
// () { |
|
||||||
// final size = MediaQuery.sizeOf(context); |
|
||||||
// double maxWidth, maxHeight; |
|
||||||
// if (kIsWeb) { |
|
||||||
// maxWidth = size.width / 7; |
|
||||||
// maxHeight = size.height / 7; |
|
||||||
// } else { |
|
||||||
// maxWidth = size.width - 80; |
|
||||||
// maxHeight = maxWidth / 2; |
|
||||||
// } |
|
||||||
// return BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight); |
|
||||||
// }(); |
|
||||||
|
|
||||||
// final shape = dialogTheme?.shape ?? |
|
||||||
// DialogTheme.of(context).shape ?? |
|
||||||
// RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)); |
|
||||||
|
|
||||||
// return Dialog( |
|
||||||
// backgroundColor: dialogTheme?.dialogBackgroundColor, |
|
||||||
// shape: shape, |
|
||||||
// child: ConstrainedBox( |
|
||||||
// constraints: constraints, |
|
||||||
// child: Padding( |
|
||||||
// padding: dialogTheme?.mediaSelectorDialogPadding ?? |
|
||||||
// const EdgeInsets.all(16), |
|
||||||
// child: Row( |
|
||||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|
||||||
// children: [ |
|
||||||
// Expanded( |
|
||||||
// child: TextButtonWithIcon( |
|
||||||
// icon: Icons.collections, |
|
||||||
// label: galleryButtonText ?? 'Gallery'.i18n, |
|
||||||
// onPressed: () => |
|
||||||
// Navigator.pop(context, MediaPickSetting.gallery), |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// const SizedBox(width: 10), |
|
||||||
// Expanded( |
|
||||||
// child: TextButtonWithIcon( |
|
||||||
// icon: Icons.link, |
|
||||||
// label: linkButtonText ?? 'Link'.i18n, |
|
||||||
// onPressed: () => |
|
||||||
// Navigator.pop(context, MediaPickSetting.link), |
|
||||||
// ), |
|
||||||
// ) |
|
||||||
// ], |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ); |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
// class TextButtonWithIcon extends StatelessWidget { |
|
||||||
// const TextButtonWithIcon({ |
|
||||||
// required this.label, |
|
||||||
// required this.icon, |
|
||||||
// required this.onPressed, |
|
||||||
// this.textStyle, |
|
||||||
// super.key, |
|
||||||
// }); |
|
||||||
|
|
||||||
// final String label; |
|
||||||
// final IconData icon; |
|
||||||
// final VoidCallback onPressed; |
|
||||||
// final TextStyle? textStyle; |
|
||||||
|
|
||||||
// @override |
|
||||||
// Widget build(BuildContext context) { |
|
||||||
// final theme = Theme.of(context); |
|
||||||
// final scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1; |
|
||||||
// final gap = scale <= 1 ? 8.0 : lerpDouble(8, 4, math.min(scale - 1, 1))!; |
|
||||||
// final buttonStyle = TextButtonTheme.of(context).style; |
|
||||||
// final shape = buttonStyle?.shape?.resolve({}) ?? |
|
||||||
// const RoundedRectangleBorder( |
|
||||||
// borderRadius: BorderRadius.all( |
|
||||||
// Radius.circular(4), |
|
||||||
// ), |
|
||||||
// ); |
|
||||||
// return Material( |
|
||||||
// shape: shape, |
|
||||||
// textStyle: textStyle ?? |
|
||||||
// theme.textButtonTheme.style?.textStyle?.resolve({}) ?? |
|
||||||
// theme.textTheme.labelLarge, |
|
||||||
// elevation: buttonStyle?.elevation?.resolve({}) ?? 0, |
|
||||||
// child: InkWell( |
|
||||||
// customBorder: shape, |
|
||||||
// onTap: onPressed, |
|
||||||
// child: Padding( |
|
||||||
// padding: const EdgeInsets.all(16), |
|
||||||
// child: Column( |
|
||||||
// mainAxisSize: MainAxisSize.min, |
|
||||||
// children: <Widget>[ |
|
||||||
// Icon(icon), |
|
||||||
// SizedBox(height: gap), |
|
||||||
// Flexible(child: Text(label)), |
|
||||||
// ], |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ); |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
// /// Default file picker. |
|
||||||
// // Future<QuillFile?> _defaultMediaPicker(QuillMediaType mediaType) async { |
|
||||||
// // final pickedFile = mediaType.isImage |
|
||||||
// // ? await ImagePicker().pickImage(source: ImageSource.gallery) |
|
||||||
// // : await ImagePicker().pickVideo(source: ImageSource.gallery); |
|
||||||
|
|
||||||
// // if (pickedFile != null) { |
|
||||||
// // return QuillFile( |
|
||||||
// // name: pickedFile.name, |
|
||||||
// // path: pickedFile.path, |
|
||||||
// // bytes: await pickedFile.readAsBytes(), |
|
||||||
// // ); |
|
||||||
// // } |
|
||||||
|
|
||||||
// // return null; |
|
||||||
// // } |
|
@ -1,62 +0,0 @@ |
|||||||
import 'package:flutter/foundation.dart' show kIsWeb; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_quill/flutter_quill.dart'; |
|
||||||
|
|
||||||
import '../../../models/config/video/editor/video_configurations.dart'; |
|
||||||
import '../../../utils/element_utils/element_utils.dart'; |
|
||||||
import '../../../utils/utils.dart'; |
|
||||||
import '../../widgets/video_app.dart'; |
|
||||||
import '../../widgets/youtube_video_app.dart'; |
|
||||||
|
|
||||||
class QuillEditorVideoEmbedBuilder extends EmbedBuilder { |
|
||||||
const QuillEditorVideoEmbedBuilder({ |
|
||||||
required this.configurations, |
|
||||||
}); |
|
||||||
|
|
||||||
final QuillEditorVideoEmbedConfigurations configurations; |
|
||||||
|
|
||||||
@override |
|
||||||
String get key => BlockEmbed.videoType; |
|
||||||
|
|
||||||
@override |
|
||||||
bool get expanded => false; |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build( |
|
||||||
BuildContext context, |
|
||||||
QuillController controller, |
|
||||||
Embed node, |
|
||||||
bool readOnly, |
|
||||||
bool inline, |
|
||||||
TextStyle textStyle, |
|
||||||
) { |
|
||||||
assert(!kIsWeb, 'Please provide video EmbedBuilder for Web'); |
|
||||||
|
|
||||||
final videoUrl = node.value.data; |
|
||||||
if (isYouTubeUrl(videoUrl)) { |
|
||||||
return YoutubeVideoApp( |
|
||||||
videoUrl: videoUrl, |
|
||||||
readOnly: readOnly, |
|
||||||
); |
|
||||||
} |
|
||||||
final ((elementSize), margin, alignment) = getElementAttributes( |
|
||||||
node, |
|
||||||
context, |
|
||||||
); |
|
||||||
|
|
||||||
final width = elementSize.width; |
|
||||||
final height = elementSize.height; |
|
||||||
return Container( |
|
||||||
width: width, |
|
||||||
height: height, |
|
||||||
margin: EdgeInsets.all(margin ?? 0.0), |
|
||||||
alignment: alignment, |
|
||||||
child: VideoApp( |
|
||||||
videoUrl: videoUrl, |
|
||||||
context: context, |
|
||||||
readOnly: readOnly, |
|
||||||
onVideoInit: configurations.onVideoInit, |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,64 +0,0 @@ |
|||||||
import 'package:flutter/widgets.dart'; |
|
||||||
import 'package:flutter_quill/flutter_quill.dart'; |
|
||||||
import 'package:universal_html/html.dart' as html; |
|
||||||
import 'package:youtube_player_flutter/youtube_player_flutter.dart' |
|
||||||
show YoutubePlayer; |
|
||||||
|
|
||||||
import '../../../models/config/video/editor/video_web_configurations.dart'; |
|
||||||
import '../../../utils/dart_ui/dart_ui_fake.dart' |
|
||||||
if (dart.library.html) '../../../utils/dart_ui/dart_ui_real.dart' as ui; |
|
||||||
import '../../../utils/element_utils/element_web_utils.dart'; |
|
||||||
import '../../../utils/utils.dart'; |
|
||||||
|
|
||||||
class QuillEditorWebVideoEmbedBuilder extends EmbedBuilder { |
|
||||||
const QuillEditorWebVideoEmbedBuilder({ |
|
||||||
required this.configurations, |
|
||||||
}); |
|
||||||
|
|
||||||
final QuillEditorWebVideoEmbedConfigurations configurations; |
|
||||||
|
|
||||||
@override |
|
||||||
String get key => BlockEmbed.videoType; |
|
||||||
|
|
||||||
@override |
|
||||||
bool get expanded => false; |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build( |
|
||||||
BuildContext context, |
|
||||||
QuillController controller, |
|
||||||
Embed node, |
|
||||||
bool readOnly, |
|
||||||
bool inline, |
|
||||||
TextStyle textStyle, |
|
||||||
) { |
|
||||||
var videoUrl = node.value.data; |
|
||||||
if (isYouTubeUrl(videoUrl)) { |
|
||||||
final youtubeID = YoutubePlayer.convertUrlToId(videoUrl); |
|
||||||
if (youtubeID != null) { |
|
||||||
videoUrl = 'https://www.youtube.com/embed/$youtubeID'; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
final (height, width, margin, alignment) = getWebElementAttributes(node); |
|
||||||
|
|
||||||
ui.PlatformViewRegistry().registerViewFactory( |
|
||||||
videoUrl, |
|
||||||
(id) => html.IFrameElement() |
|
||||||
..style.width = width |
|
||||||
..style.height = height |
|
||||||
..src = videoUrl |
|
||||||
..style.border = 'none' |
|
||||||
..style.margin = margin |
|
||||||
..style.alignSelf = alignment |
|
||||||
..attributes['loading'] = 'lazy', |
|
||||||
); |
|
||||||
|
|
||||||
return SizedBox( |
|
||||||
height: 500, |
|
||||||
child: HtmlElementView( |
|
||||||
viewType: videoUrl, |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,59 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_quill/extensions.dart' show isDesktop; |
|
||||||
import 'package:flutter_quill/translations.dart'; |
|
||||||
|
|
||||||
import '../video.dart'; |
|
||||||
|
|
||||||
class SelectVideoSourceDialog extends StatelessWidget { |
|
||||||
const SelectVideoSourceDialog({super.key}); |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
return Container( |
|
||||||
constraints: const BoxConstraints(minHeight: 200), |
|
||||||
width: double.infinity, |
|
||||||
child: SingleChildScrollView( |
|
||||||
child: Column( |
|
||||||
children: [ |
|
||||||
ListTile( |
|
||||||
title: Text(context.loc.gallery), |
|
||||||
subtitle: Text( |
|
||||||
context.loc.pickAVideoFromYourGallery, |
|
||||||
), |
|
||||||
leading: const Icon(Icons.photo_sharp), |
|
||||||
onTap: () => Navigator.of(context).pop(InsertVideoSource.gallery), |
|
||||||
), |
|
||||||
ListTile( |
|
||||||
title: Text(context.loc.camera), |
|
||||||
subtitle: Text(context.loc.recordAVideoUsingYourCamera), |
|
||||||
leading: const Icon(Icons.camera), |
|
||||||
enabled: !isDesktop(supportWeb: false), |
|
||||||
onTap: () => Navigator.of(context).pop(InsertVideoSource.camera), |
|
||||||
), |
|
||||||
ListTile( |
|
||||||
title: Text(context.loc.link), |
|
||||||
subtitle: Text( |
|
||||||
context.loc.pasteAVideoUsingALink, |
|
||||||
), |
|
||||||
leading: const Icon(Icons.link), |
|
||||||
onTap: () => Navigator.of(context).pop(InsertVideoSource.link), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Future<InsertVideoSource?> showSelectVideoSourceDialog({ |
|
||||||
required BuildContext context, |
|
||||||
}) async { |
|
||||||
final imageSource = await showModalBottomSheet<InsertVideoSource>( |
|
||||||
showDragHandle: true, |
|
||||||
context: context, |
|
||||||
constraints: const BoxConstraints(maxWidth: 640), |
|
||||||
builder: (context) => |
|
||||||
const FlutterQuillLocalizationsWidget(child: SelectVideoSourceDialog()), |
|
||||||
); |
|
||||||
return imageSource; |
|
||||||
} |
|
@ -1,188 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_quill/flutter_quill.dart'; |
|
||||||
import 'package:flutter_quill/translations.dart'; |
|
||||||
|
|
||||||
import '../../../models/config/shared_configurations.dart'; |
|
||||||
import '../../../models/config/video/toolbar/video_configurations.dart'; |
|
||||||
import '../../../services/image_picker/image_options.dart'; |
|
||||||
import '../../others/image_video_utils.dart'; |
|
||||||
import '../video.dart'; |
|
||||||
import 'select_video_source.dart'; |
|
||||||
|
|
||||||
class QuillToolbarVideoButton extends StatelessWidget { |
|
||||||
const QuillToolbarVideoButton({ |
|
||||||
required this.controller, |
|
||||||
this.options = const QuillToolbarVideoButtonOptions(), |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final QuillController controller; |
|
||||||
|
|
||||||
final QuillToolbarVideoButtonOptions options; |
|
||||||
|
|
||||||
double _iconSize(BuildContext context) { |
|
||||||
final baseFontSize = baseButtonExtraOptions(context)?.iconSize; |
|
||||||
final iconSize = options.iconSize; |
|
||||||
return iconSize ?? baseFontSize ?? kDefaultIconSize; |
|
||||||
} |
|
||||||
|
|
||||||
double _iconButtonFactor(BuildContext context) { |
|
||||||
final baseIconFactor = baseButtonExtraOptions(context)?.iconButtonFactor; |
|
||||||
final iconButtonFactor = options.iconButtonFactor; |
|
||||||
return iconButtonFactor ?? baseIconFactor ?? kDefaultIconButtonFactor; |
|
||||||
} |
|
||||||
|
|
||||||
VoidCallback? _afterButtonPressed(BuildContext context) { |
|
||||||
return options.afterButtonPressed ?? |
|
||||||
baseButtonExtraOptions(context)?.afterButtonPressed; |
|
||||||
} |
|
||||||
|
|
||||||
QuillIconTheme? _iconTheme(BuildContext context) { |
|
||||||
return options.iconTheme ?? baseButtonExtraOptions(context)?.iconTheme; |
|
||||||
} |
|
||||||
|
|
||||||
QuillToolbarBaseButtonOptions? baseButtonExtraOptions(BuildContext context) { |
|
||||||
return context.quillToolbarBaseButtonOptions; |
|
||||||
} |
|
||||||
|
|
||||||
IconData _iconData(BuildContext context) { |
|
||||||
return options.iconData ?? |
|
||||||
baseButtonExtraOptions(context)?.iconData ?? |
|
||||||
Icons.movie_creation; |
|
||||||
} |
|
||||||
|
|
||||||
String _tooltip(BuildContext context) { |
|
||||||
return options.tooltip ?? |
|
||||||
baseButtonExtraOptions(context)?.tooltip ?? |
|
||||||
'Insert video'; |
|
||||||
// ('Insert video'.i18n); |
|
||||||
} |
|
||||||
|
|
||||||
void _sharedOnPressed(BuildContext context) { |
|
||||||
_onPressedHandler(context); |
|
||||||
_afterButtonPressed(context); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
final tooltip = _tooltip(context); |
|
||||||
final iconSize = _iconSize(context); |
|
||||||
final iconButtonFactor = _iconButtonFactor(context); |
|
||||||
final iconData = _iconData(context); |
|
||||||
final childBuilder = |
|
||||||
options.childBuilder ?? baseButtonExtraOptions(context)?.childBuilder; |
|
||||||
|
|
||||||
// final iconColor = |
|
||||||
// iconTheme?.iconUnselectedFillColor ?? theme.iconTheme.color; |
|
||||||
// final iconFillColor = iconTheme?.iconUnselectedFillColor ?? |
|
||||||
// (options.fillColor ?? theme.canvasColor); |
|
||||||
|
|
||||||
if (childBuilder != null) { |
|
||||||
return childBuilder( |
|
||||||
QuillToolbarVideoButtonOptions( |
|
||||||
afterButtonPressed: _afterButtonPressed(context), |
|
||||||
iconData: iconData, |
|
||||||
dialogTheme: options.dialogTheme, |
|
||||||
iconSize: options.iconSize, |
|
||||||
iconButtonFactor: iconButtonFactor, |
|
||||||
linkRegExp: options.linkRegExp, |
|
||||||
tooltip: options.tooltip, |
|
||||||
iconTheme: options.iconTheme, |
|
||||||
videoConfigurations: options.videoConfigurations, |
|
||||||
), |
|
||||||
QuillToolbarVideoButtonExtraOptions( |
|
||||||
context: context, |
|
||||||
controller: controller, |
|
||||||
onPressed: () => _sharedOnPressed(context), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
return QuillToolbarIconButton( |
|
||||||
icon: Icon( |
|
||||||
iconData, |
|
||||||
size: iconSize * iconButtonFactor, |
|
||||||
), |
|
||||||
tooltip: tooltip, |
|
||||||
isSelected: false, |
|
||||||
onPressed: () => _sharedOnPressed(context), |
|
||||||
iconTheme: _iconTheme(context), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> _onPressedHandler(BuildContext context) async { |
|
||||||
final imagePickerService = |
|
||||||
QuillSharedExtensionsConfigurations.get(context: context) |
|
||||||
.imagePickerService; |
|
||||||
|
|
||||||
final onRequestPickVideo = options.videoConfigurations.onRequestPickVideo; |
|
||||||
if (onRequestPickVideo != null) { |
|
||||||
final videoUrl = await onRequestPickVideo(context, imagePickerService); |
|
||||||
if (videoUrl != null) { |
|
||||||
await options.videoConfigurations |
|
||||||
.onVideoInsertCallback(videoUrl, controller); |
|
||||||
await options.videoConfigurations.onVideoInsertedCallback |
|
||||||
?.call(videoUrl); |
|
||||||
} |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
final imageSource = await showSelectVideoSourceDialog(context: context); |
|
||||||
|
|
||||||
if (imageSource == null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
final videoUrl = switch (imageSource) { |
|
||||||
InsertVideoSource.gallery => |
|
||||||
(await imagePickerService.pickVideo(source: ImageSource.gallery))?.path, |
|
||||||
InsertVideoSource.camera => |
|
||||||
(await imagePickerService.pickVideo(source: ImageSource.camera))?.path, |
|
||||||
InsertVideoSource.link => |
|
||||||
context.mounted ? await _typeLink(context) : null, |
|
||||||
}; |
|
||||||
if (videoUrl == null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if (videoUrl.trim().isNotEmpty) { |
|
||||||
await options.videoConfigurations |
|
||||||
.onVideoInsertCallback(videoUrl, controller); |
|
||||||
await options.videoConfigurations.onVideoInsertedCallback?.call(videoUrl); |
|
||||||
} |
|
||||||
|
|
||||||
// if (options.onVideoPickCallback != null) { |
|
||||||
// final selector = options.mediaPickSettingSelector ?? |
|
||||||
// ImageVideoUtils.selectMediaPickSetting; |
|
||||||
// final source = await selector(context); |
|
||||||
// if (source != null) { |
|
||||||
// if (source == MediaPickSetting.gallery) { |
|
||||||
// } else { |
|
||||||
// await _typeLink(context); |
|
||||||
// } |
|
||||||
// } |
|
||||||
// } else {} |
|
||||||
} |
|
||||||
|
|
||||||
Future<String?> _typeLink(BuildContext context) async { |
|
||||||
final value = await showDialog<String>( |
|
||||||
context: context, |
|
||||||
builder: (_) => FlutterQuillLocalizationsWidget( |
|
||||||
child: TypeLinkDialog( |
|
||||||
dialogTheme: options.dialogTheme, |
|
||||||
linkType: LinkType.video, |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
return value; |
|
||||||
} |
|
||||||
|
|
||||||
// void _linkSubmitted(String? value) { |
|
||||||
// if (value != null && value.isNotEmpty) { |
|
||||||
// final index = controller.selection.baseOffset; |
|
||||||
// final length = controller.selection.extentOffset - index; |
|
||||||
|
|
||||||
// controller.replaceText(index, length, BlockEmbed.video(value), null); |
|
||||||
// } |
|
||||||
// } |
|
||||||
} |
|
@ -1,66 +0,0 @@ |
|||||||
import 'package:flutter/widgets.dart' show BuildContext; |
|
||||||
import 'package:flutter_quill/flutter_quill.dart'; |
|
||||||
import 'package:meta/meta.dart' show immutable; |
|
||||||
|
|
||||||
import '../../extensions/controller_ext.dart'; |
|
||||||
import '../../services/image_picker/s_image_picker.dart'; |
|
||||||
|
|
||||||
/// When request picking an video, for example when the video button toolbar |
|
||||||
/// clicked, it should be null in case the user didn't choose any video or |
|
||||||
/// any other reasons, and it should be the video file path as string that is |
|
||||||
/// exists in case the user picked the video successfully |
|
||||||
/// |
|
||||||
/// by default we already have a default implementation that show a dialog |
|
||||||
/// request the source for picking the video, from gallery, link or camera |
|
||||||
typedef OnRequestPickVideo = Future<String?> Function( |
|
||||||
BuildContext context, |
|
||||||
ImagePickerService imagePickerService, |
|
||||||
); |
|
||||||
|
|
||||||
/// A callback will called when inserting a video in the editor |
|
||||||
/// it have the logic that will insert the video block using the controller |
|
||||||
typedef OnVideoInsertCallback = Future<void> Function( |
|
||||||
String video, |
|
||||||
QuillController controller, |
|
||||||
); |
|
||||||
|
|
||||||
OnVideoInsertCallback defaultOnVideoInsertCallback() { |
|
||||||
return (videoUrl, controller) async { |
|
||||||
controller |
|
||||||
..skipRequestKeyboard = true |
|
||||||
..insertVideoBlock(videoUrl: videoUrl); |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
/// When a new video picked this callback will called and you might want to |
|
||||||
/// do some logic depending on your use case |
|
||||||
typedef OnVideoInsertedCallback = Future<void> Function( |
|
||||||
String video, |
|
||||||
); |
|
||||||
|
|
||||||
enum InsertVideoSource { |
|
||||||
gallery, |
|
||||||
camera, |
|
||||||
link, |
|
||||||
} |
|
||||||
|
|
||||||
/// Configurations for dealing with videos, on insert a video |
|
||||||
/// on request picking a video |
|
||||||
@immutable |
|
||||||
class QuillToolbarVideoConfigurations { |
|
||||||
const QuillToolbarVideoConfigurations({ |
|
||||||
this.onRequestPickVideo, |
|
||||||
this.onVideoInsertedCallback, |
|
||||||
OnVideoInsertCallback? onVideoInsertCallback, |
|
||||||
}) : _onVideoInsertCallback = onVideoInsertCallback; |
|
||||||
|
|
||||||
final OnRequestPickVideo? onRequestPickVideo; |
|
||||||
|
|
||||||
final OnVideoInsertedCallback? onVideoInsertedCallback; |
|
||||||
|
|
||||||
final OnVideoInsertCallback? _onVideoInsertCallback; |
|
||||||
|
|
||||||
OnVideoInsertCallback get onVideoInsertCallback { |
|
||||||
return _onVideoInsertCallback ?? defaultOnVideoInsertCallback(); |
|
||||||
} |
|
||||||
} |
|
@ -1,78 +0,0 @@ |
|||||||
import 'package:flutter/gestures.dart' show TapGestureRecognizer; |
|
||||||
import 'package:flutter/widgets.dart'; |
|
||||||
import 'package:flutter_quill/flutter_quill.dart' show DefaultStyles; |
|
||||||
import 'package:url_launcher/url_launcher.dart' show launchUrl; |
|
||||||
import 'package:youtube_player_flutter/youtube_player_flutter.dart'; |
|
||||||
|
|
||||||
class YoutubeVideoApp extends StatefulWidget { |
|
||||||
const YoutubeVideoApp({ |
|
||||||
required this.videoUrl, |
|
||||||
required this.readOnly, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final String videoUrl; |
|
||||||
final bool readOnly; |
|
||||||
|
|
||||||
@override |
|
||||||
YoutubeVideoAppState createState() => YoutubeVideoAppState(); |
|
||||||
} |
|
||||||
|
|
||||||
class YoutubeVideoAppState extends State<YoutubeVideoApp> { |
|
||||||
YoutubePlayerController? _youtubeController; |
|
||||||
|
|
||||||
@override |
|
||||||
void initState() { |
|
||||||
super.initState(); |
|
||||||
final videoId = YoutubePlayer.convertUrlToId(widget.videoUrl); |
|
||||||
if (videoId != null) { |
|
||||||
_youtubeController = YoutubePlayerController( |
|
||||||
initialVideoId: videoId, |
|
||||||
flags: const YoutubePlayerFlags( |
|
||||||
autoPlay: false, |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
final defaultStyles = DefaultStyles.getInstance(context); |
|
||||||
final youtubeController = _youtubeController; |
|
||||||
|
|
||||||
if (youtubeController == null) { |
|
||||||
if (widget.readOnly) { |
|
||||||
return RichText( |
|
||||||
text: TextSpan( |
|
||||||
text: widget.videoUrl, |
|
||||||
style: defaultStyles.link, |
|
||||||
recognizer: TapGestureRecognizer() |
|
||||||
..onTap = () => launchUrl( |
|
||||||
Uri.parse(widget.videoUrl), |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
return RichText( |
|
||||||
text: TextSpan(text: widget.videoUrl, style: defaultStyles.link), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
return YoutubePlayerBuilder( |
|
||||||
player: YoutubePlayer( |
|
||||||
controller: youtubeController, |
|
||||||
showVideoProgressIndicator: true, |
|
||||||
), |
|
||||||
builder: (context, player) { |
|
||||||
return player; |
|
||||||
}, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
_youtubeController?.dispose(); |
|
||||||
super.dispose(); |
|
||||||
} |
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
import 'package:flutter/widgets.dart' show GlobalKey; |
|
||||||
import 'package:meta/meta.dart' show immutable; |
|
||||||
|
|
||||||
@immutable |
|
||||||
class QuillEditorVideoEmbedConfigurations { |
|
||||||
const QuillEditorVideoEmbedConfigurations({ |
|
||||||
this.onVideoInit, |
|
||||||
}); |
|
||||||
|
|
||||||
/// [onVideoInit] is a callback function that gets triggered when |
|
||||||
/// a video is initialized. |
|
||||||
/// You can use this to perform actions or setup configurations related |
|
||||||
/// to video embedding. |
|
||||||
/// |
|
||||||
/// |
|
||||||
/// Example usage: |
|
||||||
/// ```dart |
|
||||||
/// onVideoInit: (videoContainerKey) { |
|
||||||
/// // Custom video initialization logic |
|
||||||
/// }, |
|
||||||
/// // Customize other callback functions as needed |
|
||||||
/// ``` |
|
||||||
final void Function(GlobalKey videoContainerKey)? onVideoInit; |
|
||||||
} |
|
@ -1,11 +0,0 @@ |
|||||||
import 'package:flutter/widgets.dart' show BoxConstraints; |
|
||||||
import 'package:meta/meta.dart' show immutable; |
|
||||||
|
|
||||||
@immutable |
|
||||||
class QuillEditorWebVideoEmbedConfigurations { |
|
||||||
const QuillEditorWebVideoEmbedConfigurations({ |
|
||||||
this.constraints, |
|
||||||
}); |
|
||||||
|
|
||||||
final BoxConstraints? constraints; |
|
||||||
} |
|
@ -1,36 +0,0 @@ |
|||||||
import 'package:flutter/widgets.dart' show Color; |
|
||||||
import 'package:flutter_quill/flutter_quill.dart'; |
|
||||||
|
|
||||||
import '../../../../embeds/video/video.dart'; |
|
||||||
|
|
||||||
class QuillToolbarVideoButtonExtraOptions |
|
||||||
extends QuillToolbarBaseButtonExtraOptions { |
|
||||||
const QuillToolbarVideoButtonExtraOptions({ |
|
||||||
required super.controller, |
|
||||||
required super.context, |
|
||||||
required super.onPressed, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
class QuillToolbarVideoButtonOptions extends QuillToolbarBaseButtonOptions< |
|
||||||
QuillToolbarVideoButtonOptions, QuillToolbarVideoButtonExtraOptions> { |
|
||||||
const QuillToolbarVideoButtonOptions({ |
|
||||||
this.linkRegExp, |
|
||||||
this.dialogTheme, |
|
||||||
this.fillColor, |
|
||||||
super.iconSize, |
|
||||||
super.iconButtonFactor, |
|
||||||
super.iconData, |
|
||||||
super.afterButtonPressed, |
|
||||||
super.tooltip, |
|
||||||
super.iconTheme, |
|
||||||
super.childBuilder, |
|
||||||
this.videoConfigurations = const QuillToolbarVideoConfigurations(), |
|
||||||
}); |
|
||||||
|
|
||||||
final RegExp? linkRegExp; |
|
||||||
final QuillDialogTheme? dialogTheme; |
|
||||||
final QuillToolbarVideoConfigurations videoConfigurations; |
|
||||||
|
|
||||||
final Color? fillColor; |
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
# Miscellaneous |
|
||||||
*.class |
|
||||||
*.log |
|
||||||
*.pyc |
|
||||||
*.swp |
|
||||||
.DS_Store |
|
||||||
.atom/ |
|
||||||
.buildlog/ |
|
||||||
.history |
|
||||||
.svn/ |
|
||||||
migrate_working_dir/ |
|
||||||
|
|
||||||
# IntelliJ related |
|
||||||
*.iml |
|
||||||
*.ipr |
|
||||||
*.iws |
|
||||||
.idea/ |
|
||||||
|
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in |
|
||||||
# VS Code which you may wish to be included in version control, so this line |
|
||||||
# is commented out by default. |
|
||||||
#.vscode/ |
|
||||||
|
|
||||||
# Flutter/Dart/Pub related |
|
||||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. |
|
||||||
/pubspec.lock |
|
||||||
**/doc/api/ |
|
||||||
.dart_tool/ |
|
||||||
.packages |
|
||||||
build/ |
|
@ -1,10 +0,0 @@ |
|||||||
# This file tracks properties of this Flutter project. |
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc. |
|
||||||
# |
|
||||||
# This file should be version controlled and should not be manually edited. |
|
||||||
|
|
||||||
version: |
|
||||||
revision: "d211f42860350d914a5ad8102f9ec32764dc6d06" |
|
||||||
channel: "stable" |
|
||||||
|
|
||||||
project_type: package |
|
File diff suppressed because it is too large
Load Diff
@ -1,21 +0,0 @@ |
|||||||
MIT License |
|
||||||
|
|
||||||
Copyright (c) 2023 Flutter Quill Team |
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||||
of this software and associated documentation files (the "Software"), to deal |
|
||||||
in the Software without restriction, including without limitation the rights |
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||||
copies of the Software, and to permit persons to whom the Software is |
|
||||||
furnished to do so, subject to the following conditions: |
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all |
|
||||||
copies or substantial portions of the Software. |
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||||
SOFTWARE. |
|
@ -1,41 +0,0 @@ |
|||||||
# Flutter Quill HTML |
|
||||||
A extension for [flutter_quill](https://pub.dev/packages/flutter_quill) package to add support for dealing with conversion to/from html |
|
||||||
|
|
||||||
It uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) package to convert the the delta to HTML |
|
||||||
|
|
||||||
This library is **experimental** and the support might be dropped at anytime. |
|
||||||
|
|
||||||
## Features |
|
||||||
|
|
||||||
```markdown |
|
||||||
- Easy to use |
|
||||||
- Support Flutter Quill package |
|
||||||
``` |
|
||||||
|
|
||||||
## Getting started |
|
||||||
|
|
||||||
```yaml |
|
||||||
dependencies: |
|
||||||
quill_html_converter: ^<latest-version-here> |
|
||||||
``` |
|
||||||
|
|
||||||
## Usage |
|
||||||
|
|
||||||
First, you need to [setup](../README.md#usage) the `flutter_quill` first |
|
||||||
|
|
||||||
Then you can simply convert to/from HTML |
|
||||||
|
|
||||||
```dart |
|
||||||
import 'package:quill_html_converter/quill_html_converter.dart'; |
|
||||||
|
|
||||||
// Convert Delta to HTML |
|
||||||
final html = _controller.document.toDelta().toHtml(); |
|
||||||
|
|
||||||
// Load Delta document using HTML |
|
||||||
_controller.document = |
|
||||||
Document.fromDelta(Document.fromHtml(html)); |
|
||||||
``` |
|
||||||
|
|
||||||
## Additional information |
|
||||||
|
|
||||||
This will be updated soon. |
|
@ -1,36 +0,0 @@ |
|||||||
include: package:flutter_lints/flutter.yaml |
|
||||||
|
|
||||||
analyzer: |
|
||||||
errors: |
|
||||||
undefined_prefixed_name: ignore |
|
||||||
unsafe_html: ignore |
|
||||||
linter: |
|
||||||
rules: |
|
||||||
always_declare_return_types: true |
|
||||||
always_put_required_named_parameters_first: true |
|
||||||
annotate_overrides: true |
|
||||||
avoid_empty_else: true |
|
||||||
avoid_escaping_inner_quotes: true |
|
||||||
avoid_print: true |
|
||||||
avoid_redundant_argument_values: true |
|
||||||
avoid_types_on_closure_parameters: true |
|
||||||
avoid_void_async: true |
|
||||||
cascade_invocations: true |
|
||||||
directives_ordering: true |
|
||||||
omit_local_variable_types: true |
|
||||||
prefer_const_constructors: true |
|
||||||
prefer_const_constructors_in_immutables: true |
|
||||||
prefer_const_declarations: true |
|
||||||
prefer_final_fields: true |
|
||||||
prefer_final_in_for_each: true |
|
||||||
prefer_final_locals: true |
|
||||||
prefer_initializing_formals: true |
|
||||||
prefer_int_literals: true |
|
||||||
prefer_interpolation_to_compose_strings: true |
|
||||||
prefer_relative_imports: true |
|
||||||
prefer_single_quotes: true |
|
||||||
sort_constructors_first: true |
|
||||||
sort_unnamed_constructors_first: true |
|
||||||
unnecessary_lambdas: true |
|
||||||
unnecessary_parenthesis: true |
|
||||||
unnecessary_string_interpolations: true |
|
@ -1,28 +0,0 @@ |
|||||||
library quill_html_converter; |
|
||||||
|
|
||||||
import 'package:dart_quill_delta/dart_quill_delta.dart'; |
|
||||||
import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart' |
|
||||||
as conventer show ConverterOptions, QuillDeltaToHtmlConverter; |
|
||||||
|
|
||||||
typedef ConverterOptions = conventer.ConverterOptions; |
|
||||||
|
|
||||||
/// A extension for [Delta] which comes from `flutter_quill` to extends |
|
||||||
/// the functionality of it to support converting the [Delta] to/from HTML |
|
||||||
extension DeltaHtmlExt on Delta { |
|
||||||
/// Convert the [Delta] instance to HTML Raw string |
|
||||||
/// |
|
||||||
/// It will run using the following steps: |
|
||||||
/// |
|
||||||
/// 1. Convert the [Delta] to json using [toJson] |
|
||||||
/// 2. Cast the json map as `List<Map<String, dynamic>>` |
|
||||||
/// 3. Pass it to the conventer `vsc_quill_delta_to_html` which is a package |
|
||||||
/// that designed specifically for converting the quill delta to html |
|
||||||
String toHtml({ConverterOptions? options}) { |
|
||||||
final json = toJson(); |
|
||||||
final html = conventer.QuillDeltaToHtmlConverter( |
|
||||||
List.castFrom(json), |
|
||||||
options, |
|
||||||
).convert(); |
|
||||||
return html; |
|
||||||
} |
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
name: quill_html_converter |
|
||||||
description: A extension for flutter_quill package to add support for dealing with conversion to/from html |
|
||||||
version: 9.2.6 |
|
||||||
homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_html_converter/ |
|
||||||
repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_html_converter/ |
|
||||||
issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ |
|
||||||
documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_html_converter/ |
|
||||||
|
|
||||||
topics: |
|
||||||
- ui |
|
||||||
- widgets |
|
||||||
- widget |
|
||||||
- rich-text-editor |
|
||||||
- quill |
|
||||||
|
|
||||||
environment: |
|
||||||
sdk: '>=3.1.5 <4.0.0' |
|
||||||
flutter: ">=1.17.0" |
|
||||||
|
|
||||||
dependencies: |
|
||||||
flutter: |
|
||||||
sdk: flutter |
|
||||||
vsc_quill_delta_to_html: ^1.0.3 |
|
||||||
html2md: ^1.3.1 |
|
||||||
markdown: ^7.1.1 |
|
||||||
charcode: ^1.3.1 |
|
||||||
collection: ^1.18.0 |
|
||||||
dart_quill_delta: ^0.0.1 |
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies: |
|
||||||
flutter_test: |
|
||||||
sdk: flutter |
|
||||||
flutter_lints: ^3.0.1 |
|
||||||
|
|
||||||
flutter: |
|
||||||
|
|
||||||
uses-material-design: true |
|
@ -1,3 +0,0 @@ |
|||||||
dependency_overrides: |
|
||||||
dart_quill_delta: |
|
||||||
path: ../dart_quill_delta |
|
@ -1,7 +0,0 @@ |
|||||||
import 'package:flutter_test/flutter_test.dart'; |
|
||||||
|
|
||||||
void main() { |
|
||||||
test('No tests for now', () { |
|
||||||
expect(true, true); |
|
||||||
}); |
|
||||||
} |
|
@ -1,29 +0,0 @@ |
|||||||
# Miscellaneous |
|
||||||
*.class |
|
||||||
*.log |
|
||||||
*.pyc |
|
||||||
*.swp |
|
||||||
.DS_Store |
|
||||||
.atom/ |
|
||||||
.buildlog/ |
|
||||||
.history |
|
||||||
.svn/ |
|
||||||
migrate_working_dir/ |
|
||||||
|
|
||||||
# IntelliJ related |
|
||||||
*.iml |
|
||||||
*.ipr |
|
||||||
*.iws |
|
||||||
.idea/ |
|
||||||
|
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in |
|
||||||
# VS Code which you may wish to be included in version control, so this line |
|
||||||
# is commented out by default. |
|
||||||
#.vscode/ |
|
||||||
|
|
||||||
# Flutter/Dart/Pub related |
|
||||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. |
|
||||||
/pubspec.lock |
|
||||||
**/doc/api/ |
|
||||||
.dart_tool/ |
|
||||||
build/ |
|
@ -1,10 +0,0 @@ |
|||||||
# This file tracks properties of this Flutter project. |
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc. |
|
||||||
# |
|
||||||
# This file should be version controlled and should not be manually edited. |
|
||||||
|
|
||||||
version: |
|
||||||
revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" |
|
||||||
channel: "stable" |
|
||||||
|
|
||||||
project_type: package |
|
File diff suppressed because it is too large
Load Diff
@ -1,21 +0,0 @@ |
|||||||
MIT License |
|
||||||
|
|
||||||
Copyright (c) 2023 Flutter Quill Team |
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||||
of this software and associated documentation files (the "Software"), to deal |
|
||||||
in the Software without restriction, including without limitation the rights |
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||||
copies of the Software, and to permit persons to whom the Software is |
|
||||||
furnished to do so, subject to the following conditions: |
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all |
|
||||||
copies or substantial portions of the Software. |
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||||
SOFTWARE. |
|
@ -1,34 +0,0 @@ |
|||||||
# Flutter Quill Pdf |
|
||||||
A extension for [flutter_quill](https://pub.dev/packages/flutter_quill) package to add support for dealing with conversion to Pdf |
|
||||||
|
|
||||||
It uses [quill_html_converter](https://pub.dev/packages/quill_html_converter) package to convert the the delta to Html and [htmltopdfwidgets](https://pub.dev/packages/htmltopdfwidgets) to convert the Html to Pdf |
|
||||||
|
|
||||||
This library is **experimental** and the support might be dropped at anytime. |
|
||||||
|
|
||||||
## Features |
|
||||||
|
|
||||||
```markdown |
|
||||||
- Easy to use |
|
||||||
- Support Flutter Quill package |
|
||||||
``` |
|
||||||
|
|
||||||
## Getting started |
|
||||||
|
|
||||||
```yaml |
|
||||||
dependencies: |
|
||||||
quill_pdf_converter: ^<latest-version-here> |
|
||||||
``` |
|
||||||
|
|
||||||
## Usage |
|
||||||
|
|
||||||
First, you need to [setup](../README.md#usage) the `flutter_quill` first |
|
||||||
|
|
||||||
Then you can simply convert to PDF |
|
||||||
|
|
||||||
```dart |
|
||||||
import 'package:quill_pdf_converter/quill_pdf_converter.dart'; |
|
||||||
|
|
||||||
// Convert Delta to Pdf |
|
||||||
final pdfWidgets = _controller.document.toDelta().toPdf(); |
|
||||||
|
|
||||||
``` |
|
@ -1,36 +0,0 @@ |
|||||||
include: package:flutter_lints/flutter.yaml |
|
||||||
|
|
||||||
analyzer: |
|
||||||
errors: |
|
||||||
undefined_prefixed_name: ignore |
|
||||||
unsafe_html: ignore |
|
||||||
linter: |
|
||||||
rules: |
|
||||||
always_declare_return_types: true |
|
||||||
always_put_required_named_parameters_first: true |
|
||||||
annotate_overrides: true |
|
||||||
avoid_empty_else: true |
|
||||||
avoid_escaping_inner_quotes: true |
|
||||||
avoid_print: true |
|
||||||
avoid_redundant_argument_values: true |
|
||||||
avoid_types_on_closure_parameters: true |
|
||||||
avoid_void_async: true |
|
||||||
cascade_invocations: true |
|
||||||
directives_ordering: true |
|
||||||
omit_local_variable_types: true |
|
||||||
prefer_const_constructors: true |
|
||||||
prefer_const_constructors_in_immutables: true |
|
||||||
prefer_const_declarations: true |
|
||||||
prefer_final_fields: true |
|
||||||
prefer_final_in_for_each: true |
|
||||||
prefer_final_locals: true |
|
||||||
prefer_initializing_formals: true |
|
||||||
prefer_int_literals: true |
|
||||||
prefer_interpolation_to_compose_strings: true |
|
||||||
prefer_relative_imports: true |
|
||||||
prefer_single_quotes: true |
|
||||||
sort_constructors_first: true |
|
||||||
sort_unnamed_constructors_first: true |
|
||||||
unnecessary_lambdas: true |
|
||||||
unnecessary_parenthesis: true |
|
||||||
unnecessary_string_interpolations: true |
|
@ -1,22 +0,0 @@ |
|||||||
library quill_pdf_converter; |
|
||||||
|
|
||||||
import 'package:dart_quill_delta/dart_quill_delta.dart'; |
|
||||||
import 'package:htmltopdfwidgets/htmltopdfwidgets.dart'; |
|
||||||
import 'package:meta/meta.dart'; |
|
||||||
import 'package:pdf/widgets.dart' as pw; |
|
||||||
import 'package:quill_html_converter/quill_html_converter.dart'; |
|
||||||
|
|
||||||
/// Extension on [Delta] to add extra functions for converting to Pdf |
|
||||||
extension DeltaPdfExt on Delta { |
|
||||||
/// First convert to Html then to Pdf |
|
||||||
@experimental |
|
||||||
Future<List<pw.Widget>> toPdf() async { |
|
||||||
final html = toHtml(); |
|
||||||
return HTMLToPdf().convert( |
|
||||||
html, |
|
||||||
fontFallback: [ |
|
||||||
pw.Font.symbol(), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,34 +0,0 @@ |
|||||||
name: quill_pdf_converter |
|
||||||
description: A extension for flutter_quill package to add support for dealing with conversion to pdf |
|
||||||
version: 9.2.6 |
|
||||||
homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_pdf_converter/ |
|
||||||
repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_pdf_converter/ |
|
||||||
issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ |
|
||||||
documentation: https://github.com/singerdmx/flutter-quill/tree/master/quill_pdf_converter/ |
|
||||||
|
|
||||||
topics: |
|
||||||
- ui |
|
||||||
- widgets |
|
||||||
- widget |
|
||||||
- rich-text-editor |
|
||||||
- quill |
|
||||||
|
|
||||||
environment: |
|
||||||
sdk: '>=3.2.3 <4.0.0' |
|
||||||
flutter: ">=1.17.0" |
|
||||||
|
|
||||||
dependencies: |
|
||||||
flutter: |
|
||||||
sdk: flutter |
|
||||||
dart_quill_delta: ^0.0.1 |
|
||||||
quill_html_converter: ^9.1.0 |
|
||||||
pdf: ^3.10.7 |
|
||||||
htmltopdfwidgets: ^0.0.9+2 |
|
||||||
meta: ^1.10.0 |
|
||||||
|
|
||||||
dev_dependencies: |
|
||||||
flutter_test: |
|
||||||
sdk: flutter |
|
||||||
flutter_lints: ^3.0.1 |
|
||||||
|
|
||||||
flutter: |
|
@ -1,5 +0,0 @@ |
|||||||
dependency_overrides: |
|
||||||
dart_quill_delta: |
|
||||||
path: ../dart_quill_delta |
|
||||||
quill_html_converter: |
|
||||||
path: ../quill_html_converter |
|
@ -1,7 +0,0 @@ |
|||||||
import 'package:flutter_test/flutter_test.dart'; |
|
||||||
|
|
||||||
void main() { |
|
||||||
test('No tests for now', () { |
|
||||||
expect(true, true); |
|
||||||
}); |
|
||||||
} |
|
Loading…
Reference in new issue