dartlangeditorflutterflutter-appsflutter-examplesflutter-packageflutter-widgetquillquill-deltaquilljsreactquillrich-textrich-text-editorwysiwygwysiwyg-editor
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
223 lines
6.8 KiB
223 lines
6.8 KiB
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<LinkDialog> { |
|
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<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 { |
|
final index = controller.selection.baseOffset; |
|
final length = controller.selection.extentOffset - index; |
|
|
|
String? imageUrl; |
|
if (kIsWeb) { |
|
assert( |
|
webImagePickImpl != null, |
|
'Please provide webImagePickImpl for Web ' |
|
'(check out example directory for how to do it)'); |
|
imageUrl = await webImagePickImpl!(onImagePickCallback); |
|
} else if (isMobile()) { |
|
imageUrl = await _pickImage(imageSource, onImagePickCallback); |
|
} else { |
|
assert(filePickImpl != null, 'Desktop must provide filePickImpl'); |
|
imageUrl = |
|
await _pickImageDesktop(context, filePickImpl!, onImagePickCallback); |
|
} |
|
|
|
if (imageUrl != null) { |
|
controller.replaceText(index, length, BlockEmbed.image(imageUrl), null); |
|
} |
|
} |
|
|
|
static Future<String?> _pickImage( |
|
ImageSource source, OnImagePickCallback onImagePickCallback) async { |
|
final pickedFile = await ImagePicker().pickImage(source: source); |
|
if (pickedFile == null) { |
|
return null; |
|
} |
|
|
|
return onImagePickCallback(File(pickedFile.path)); |
|
} |
|
|
|
static Future<String?> _pickImageDesktop( |
|
BuildContext context, |
|
FilePickImpl filePickImpl, |
|
OnImagePickCallback onImagePickCallback) async { |
|
final filePath = await filePickImpl(context); |
|
if (filePath == null || filePath.isEmpty) return null; |
|
|
|
final file = File(filePath); |
|
return onImagePickCallback(file); |
|
} |
|
|
|
/// For video picking logic |
|
static Future<void> handleVideoButtonTap( |
|
BuildContext context, |
|
QuillController controller, |
|
ImageSource videoSource, |
|
OnVideoPickCallback onVideoPickCallback, |
|
{FilePickImpl? filePickImpl, |
|
WebVideoPickImpl? webVideoPickImpl}) async { |
|
final index = controller.selection.baseOffset; |
|
final length = controller.selection.extentOffset - index; |
|
|
|
String? videoUrl; |
|
if (kIsWeb) { |
|
assert( |
|
webVideoPickImpl != null, |
|
'Please provide webVideoPickImpl for Web ' |
|
'(check out example directory for how to do it)'); |
|
videoUrl = await webVideoPickImpl!(onVideoPickCallback); |
|
} else if (isMobile()) { |
|
videoUrl = await _pickVideo(videoSource, onVideoPickCallback); |
|
} else { |
|
assert(filePickImpl != null, 'Desktop must provide filePickImpl'); |
|
videoUrl = |
|
await _pickVideoDesktop(context, filePickImpl!, onVideoPickCallback); |
|
} |
|
|
|
if (videoUrl != null) { |
|
controller.replaceText(index, length, BlockEmbed.video(videoUrl), null); |
|
} |
|
} |
|
|
|
static Future<String?> _pickVideo( |
|
ImageSource source, OnVideoPickCallback onVideoPickCallback) async { |
|
final pickedFile = await ImagePicker().pickVideo(source: source); |
|
if (pickedFile == null) { |
|
return null; |
|
} |
|
|
|
return onVideoPickCallback(File(pickedFile.path)); |
|
} |
|
|
|
static Future<String?> _pickVideoDesktop( |
|
BuildContext context, |
|
FilePickImpl filePickImpl, |
|
OnVideoPickCallback onVideoPickCallback) async { |
|
final filePath = await filePickImpl(context); |
|
if (filePath == null || filePath.isEmpty) return null; |
|
|
|
final file = File(filePath); |
|
return onVideoPickCallback(file); |
|
} |
|
}
|
|
|