Add `ImageButton2`

pull/1187/head
bambinoua 2 years ago
parent c3e156bb3d
commit 748ddfba58
  1. 299
      flutter_quill_extensions/lib/embeds/toolbar/image_button2.dart
  2. 14
      flutter_quill_extensions/lib/flutter_quill_extensions.dart
  3. 1
      lib/flutter_quill.dart

@ -0,0 +1,299 @@
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';
import 'image_video_utils.dart' hide LinkDialog;
/// Alternative version of [ImageButton]. This widget has more customization
/// and uses dialog similar to one which is used on [http://quilljs.com].
class ImageButton2 extends StatelessWidget {
const ImageButton2({
required this.controller,
required this.icon,
this.iconSize = kDefaultIconSize,
this.fillColor,
this.onImagePickCallback,
this.webImagePickImpl,
this.filePickImpl,
this.mediaPickSettingSelector,
this.iconTheme,
this.dialogTheme,
this.tooltip,
this.childrenSpacing = 16.0,
this.contentPadding = const EdgeInsets.all(16),
this.constraints,
this.buttonSize,
this.labelText,
this.hintText,
this.buttonText,
this.autovalidateMode = AutovalidateMode.disabled,
Key? key,
this.validationMessage,
}) : super(key: key);
final QuillController controller;
final IconData icon;
final double iconSize;
final Color? fillColor;
final OnImagePickCallback? onImagePickCallback;
final WebImagePickImpl? webImagePickImpl;
final FilePickImpl? filePickImpl;
final MediaPickSettingSelector? mediaPickSettingSelector;
final QuillIconTheme? iconTheme;
final QuillDialogTheme? dialogTheme;
final String? tooltip;
/// The margin between child widgets in the dialog.
final double childrenSpacing;
/// The padding for content of dialog.
final EdgeInsetsGeometry contentPadding;
/// The constrains for dialog.
final BoxConstraints? constraints;
/// The size of dialog buttons.
final Size? buttonSize;
/// 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;
final AutovalidateMode autovalidateMode;
final String? validationMessage;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final iconColor = iconTheme?.iconUnselectedColor ?? theme.iconTheme.color;
final iconFillColor =
iconTheme?.iconUnselectedFillColor ?? fillColor ?? theme.canvasColor;
return QuillIconButton(
icon: Icon(icon, size: iconSize, color: iconColor),
tooltip: tooltip,
highlightElevation: 0,
hoverElevation: 0,
size: iconSize * 1.77,
fillColor: iconFillColor,
borderRadius: iconTheme?.borderRadius ?? 2,
onPressed: () => _onPressedHandler(context),
);
}
Future<void> _onPressedHandler(BuildContext context) async {
if (onImagePickCallback != null) {
final selector =
mediaPickSettingSelector ?? ImageVideoUtils.selectMediaPickSetting;
final source = await selector(context);
if (source != null) {
if (source == MediaPickSetting.Gallery) {
_pickImage(context);
} else {
_typeLink(context);
}
}
} else {
_typeLink(context);
}
}
void _pickImage(BuildContext context) {
ImageVideoUtils.handleImageButtonTap(
context,
controller,
ImageSource.gallery,
onImagePickCallback!,
filePickImpl: filePickImpl,
webImagePickImpl: webImagePickImpl,
);
}
void _typeLink(BuildContext context) {
showDialog<String>(
context: context,
builder: (_) => EmbedLinkDialog(
dialogTheme: dialogTheme,
constraints: constraints,
labelText: labelText,
childrenSpacing: childrenSpacing,
autovalidateMode: autovalidateMode,
validationMessage: validationMessage,
buttonSize: buttonSize,
),
).then(_linkSubmitted);
}
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.image(value), null);
}
}
}
///
class EmbedLinkDialog extends StatefulWidget {
const EmbedLinkDialog({
Key? key,
this.link,
this.dialogTheme,
this.contentPadding = const EdgeInsets.all(16),
this.childrenSpacing = 16.0,
this.constraints,
this.buttonSize,
this.labelText,
this.hintText,
this.buttonText,
this.autovalidateMode = AutovalidateMode.disabled,
this.validationMessage,
}) : assert(childrenSpacing > 0),
super(key: key);
final String? link;
final QuillDialogTheme? dialogTheme;
/// The margin between child widgets in the dialog.
final double childrenSpacing;
/// The padding for content of dialog.
final EdgeInsetsGeometry contentPadding;
/// The constrains for dialog.
final BoxConstraints? constraints;
/// The size of dialog buttons.
final Size? buttonSize;
/// 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;
final AutovalidateMode autovalidateMode;
final String? validationMessage;
@override
State<EmbedLinkDialog> createState() => _EmbedLinkDialogState();
}
class _EmbedLinkDialogState extends State<EmbedLinkDialog> {
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 mediaQuery = MediaQuery.of(context);
final maxWidth =
kIsWeb ? mediaQuery.size.width / 4 : mediaQuery.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 link'.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.contentPadding,
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.linkRegExp.hasMatch(value!)) {
return widget.validationMessage ?? 'That is not a valid URL';
}
return null;
}
}

@ -14,13 +14,15 @@ export 'embeds/embed_types.dart';
export 'embeds/toolbar/camera_button.dart';
export 'embeds/toolbar/formula_button.dart';
export 'embeds/toolbar/image_button.dart';
export 'embeds/toolbar/image_button2.dart';
export 'embeds/toolbar/image_video_utils.dart';
export 'embeds/toolbar/video_button.dart';
export 'embeds/utils.dart';
class FlutterQuillEmbeds {
static List<EmbedBuilder> builders(
{void Function(GlobalKey videoContainerKey)? onVideoInit}) =>
static List<EmbedBuilder> builders({
void Function(GlobalKey videoContainerKey)? onVideoInit,
}) =>
[
ImageEmbedBuilder(),
VideoEmbedBuilder(onVideoInit: onVideoInit),
@ -43,8 +45,8 @@ class FlutterQuillEmbeds {
FilePickImpl? filePickImpl,
WebImagePickImpl? webImagePickImpl,
WebVideoPickImpl? webVideoPickImpl,
}) {
return [
}) =>
[
if (showImageButton)
(controller, toolbarIconSize, iconTheme, dialogTheme) => ImageButton(
icon: Icons.image,
@ -87,7 +89,8 @@ class FlutterQuillEmbeds {
iconTheme: iconTheme,
),
if (showFormulaButton)
(controller, toolbarIconSize, iconTheme, dialogTheme) => FormulaButton(
(controller, toolbarIconSize, iconTheme, dialogTheme) =>
FormulaButton(
icon: Icons.functions,
iconSize: toolbarIconSize,
tooltip: formulaButtonTooltip,
@ -96,5 +99,4 @@ class FlutterQuillEmbeds {
dialogTheme: dialogTheme,
)
];
}
}

@ -18,6 +18,7 @@ export 'src/models/themes/quill_custom_button.dart';
export 'src/models/themes/quill_dialog_theme.dart';
export 'src/models/themes/quill_icon_theme.dart';
export 'src/utils/embeds.dart';
export 'src/utils/widgets.dart';
export 'src/widgets/controller.dart';
export 'src/widgets/default_styles.dart';
export 'src/widgets/editor.dart';

Loading…
Cancel
Save