import 'dart:io' show File; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_quill/extensions.dart'; import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/translations.dart'; import 'package:image_picker/image_picker.dart'; import '../embed_types.dart'; class LinkDialog extends StatefulWidget { const LinkDialog({ this.dialogTheme, this.link, this.linkRegExp, Key? key, }) : super(key: key); final QuillDialogTheme? dialogTheme; final String? link; final RegExp? linkRegExp; @override LinkDialogState createState() => LinkDialogState(); } class LinkDialogState extends State { late String _link; late TextEditingController _controller; late RegExp _linkRegExp; @override void initState() { super.initState(); _link = widget.link ?? ''; _controller = TextEditingController(text: _link); // TODO: Consider replace the default Regex with this one // Since that is not the reason I sent the changes then I will not edit it // final defaultLinkNonSecureRegExp = RegExp(r'https?://.*?\.(?:png|jpe?g|gif|bmp|webp|tiff?)'); // Not secure // final defaultLinkRegExp = RegExp(r'https://.*?\.(?:png|jpe?g|gif|bmp|webp|tiff?)'); // Secure // _linkRegExp = widget.linkRegExp ?? defaultLinkRegExp; _linkRegExp = widget.linkRegExp ?? AutoFormatMultipleLinksRule.linkRegExp; } @override Widget build(BuildContext context) { return AlertDialog( backgroundColor: widget.dialogTheme?.dialogBackgroundColor, content: TextField( keyboardType: TextInputType.multiline, maxLines: null, style: widget.dialogTheme?.inputTextStyle, decoration: InputDecoration( labelText: 'Paste a link'.i18n, labelStyle: widget.dialogTheme?.labelTextStyle, floatingLabelStyle: widget.dialogTheme?.labelTextStyle), autofocus: true, onChanged: _linkChanged, controller: _controller, ), actions: [ TextButton( onPressed: _link.isNotEmpty && _linkRegExp.hasMatch(_link) ? _applyLink : null, child: Text( 'Ok'.i18n, style: widget.dialogTheme?.labelTextStyle, ), ), ], ); } void _linkChanged(String value) { setState(() { _link = value; }); } void _applyLink() { Navigator.pop(context, _link.trim()); } } class ImageVideoUtils { static Future selectMediaPickSetting( BuildContext context, ) => showDialog( 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 handleImageButtonTap( BuildContext context, QuillController controller, ImageSource imageSource, OnImagePickCallback onImagePickCallback, {FilePickImpl? filePickImpl, WebImagePickImpl? webImagePickImpl}) async { final index = controller.selection.baseOffset; final length = controller.selection.extentOffset - index; String? imageUrl; if (kIsWeb) { assert( webImagePickImpl != null, 'Please provide webImagePickImpl for Web ' '(check out example directory for how to do it)'); imageUrl = await webImagePickImpl!(onImagePickCallback); } else if (isMobile()) { imageUrl = await _pickImage(imageSource, onImagePickCallback); } else { assert(filePickImpl != null, 'Desktop must provide filePickImpl'); imageUrl = await _pickImageDesktop(context, filePickImpl!, onImagePickCallback); } if (imageUrl != null) { controller.replaceText(index, length, BlockEmbed.image(imageUrl), null); } } static Future _pickImage( ImageSource source, OnImagePickCallback onImagePickCallback) async { final pickedFile = await ImagePicker().pickImage(source: source); if (pickedFile == null) { return null; } return onImagePickCallback(File(pickedFile.path)); } static Future _pickImageDesktop( BuildContext context, FilePickImpl filePickImpl, OnImagePickCallback onImagePickCallback) async { final filePath = await filePickImpl(context); if (filePath == null || filePath.isEmpty) return null; final file = File(filePath); return onImagePickCallback(file); } /// For video picking logic static Future handleVideoButtonTap( BuildContext context, QuillController controller, ImageSource videoSource, OnVideoPickCallback onVideoPickCallback, {FilePickImpl? filePickImpl, WebVideoPickImpl? webVideoPickImpl}) async { final index = controller.selection.baseOffset; final length = controller.selection.extentOffset - index; String? videoUrl; if (kIsWeb) { assert( webVideoPickImpl != null, 'Please provide webVideoPickImpl for Web ' '(check out example directory for how to do it)'); videoUrl = await webVideoPickImpl!(onVideoPickCallback); } else if (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 _pickVideo( ImageSource source, OnVideoPickCallback onVideoPickCallback) async { final pickedFile = await ImagePicker().pickVideo(source: source); if (pickedFile == null) { return null; } return onVideoPickCallback(File(pickedFile.path)); } static Future _pickVideoDesktop( BuildContext context, FilePickImpl filePickImpl, OnVideoPickCallback onVideoPickCallback) async { final filePath = await filePickImpl(context); if (filePath == null || filePath.isEmpty) return null; final file = File(filePath); return onVideoPickCallback(file); } }