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