More improvemennts and one little feature

pull/1428/head
Ahmed Hnewa 2 years ago
parent 3501f7c180
commit 4488724194
No known key found for this signature in database
GPG Key ID: C488CC70BBCEF0D1
  1. 2
      CHANGELOG.md
  2. 4
      example/lib/pages/home_page.dart
  3. 8
      example/lib/universal_ui/universal_ui.dart
  4. 8
      example/lib/widgets/responsive_widget.dart
  5. 2
      flutter_quill_extensions/CHANGELOG.md
  6. 331
      flutter_quill_extensions/lib/embeds/builders.dart
  7. 13
      flutter_quill_extensions/lib/embeds/toolbar/media_button.dart
  8. 6
      flutter_quill_extensions/lib/embeds/widgets/image.dart
  9. 11
      flutter_quill_extensions/lib/embeds/widgets/image_resizer.dart
  10. 6
      lib/src/models/documents/attribute.dart
  11. 14
      lib/src/models/rules/rule.dart
  12. 6
      lib/src/utils/platform.dart
  13. 2
      lib/src/widgets/editor.dart
  14. 2
      lib/src/widgets/raw_editor.dart
  15. 2
      lib/src/widgets/text_block.dart
  16. 5
      lib/src/widgets/toolbar/link_style_button2.dart

@ -1,5 +1,7 @@
# [7.4.12]
- Update the minimum version of device_info_plus to 9.1.0.
- Custom style attrbuites for platforms other than mobile (alignment, margin, width, height)
- Improve performance by reduce numbers of widget rebuilt by listen to media query for only the needed things
-
# [7.4.11]
- Add sw locale.

@ -104,7 +104,7 @@ class _HomePageState extends State<HomePage> {
),
drawer: Container(
constraints:
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.7),
BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width * 0.7),
color: Colors.grey.shade800,
child: _buildMenuBar(context),
),
@ -408,7 +408,7 @@ class _HomePageState extends State<HomePage> {
);
Widget _buildMenuBar(BuildContext context) {
final size = MediaQuery.of(context).size;
final size = MediaQuery.sizeOf(context);
const itemStyle = TextStyle(
color: Colors.white,
fontSize: 18,

@ -45,7 +45,7 @@ class ImageEmbedBuilderWeb extends EmbedBuilder {
// TODO: handle imageUrl of base64
return const SizedBox();
}
final size = MediaQuery.of(context).size;
final size = MediaQuery.sizeOf(context);
UniversalUI().platformViewRegistry.registerViewFactory(imageUrl, (viewId) {
return html.ImageElement()
..src = imageUrl
@ -61,7 +61,7 @@ class ImageEmbedBuilderWeb extends EmbedBuilder {
: size.width * 0.2,
),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.45,
height: MediaQuery.sizeOf(context).height * 0.45,
child: HtmlElementView(
viewType: imageUrl,
),
@ -94,8 +94,8 @@ class VideoEmbedBuilderWeb extends EmbedBuilder {
UniversalUI().platformViewRegistry.registerViewFactory(
videoUrl,
(id) => html.IFrameElement()
..width = MediaQuery.of(context).size.width.toString()
..height = MediaQuery.of(context).size.height.toString()
..width = MediaQuery.sizeOf(context).width.toString()
..height = MediaQuery.sizeOf(context).height.toString()
..src = videoUrl
..style.border = 'none');

@ -13,16 +13,16 @@ class ResponsiveWidget extends StatelessWidget {
final Widget? smallScreen;
static bool isSmallScreen(BuildContext context) {
return MediaQuery.of(context).size.width < 800;
return MediaQuery.sizeOf(context).width < 800;
}
static bool isLargeScreen(BuildContext context) {
return MediaQuery.of(context).size.width > 1200;
return MediaQuery.sizeOf(context).width > 1200;
}
static bool isMediumScreen(BuildContext context) {
return MediaQuery.of(context).size.width >= 800 &&
MediaQuery.of(context).size.width <= 1200;
return MediaQuery.sizeOf(context).width >= 800 &&
MediaQuery.sizeOf(context).width <= 1200;
}
@override

@ -2,6 +2,8 @@
- Provide a way to use custom image provider for the image widgets
- Provide a way to handle different errors in image widgets
- Two bug fixes related to pick the image and capture it using the camera
- Add support for image resizing in desktop when force using mobile context menu
- Improve performance by reduce numbers of widget rebuilt by listen to media query for only the needed things
## 0.5.1
- Fix warrning "The platformViewRegistry getter is deprecated and will be removed in a future release. Please import it from dart:ui_web instead."

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io' show File;
import 'package:flutter/cupertino.dart';
@ -54,40 +55,68 @@ class ImageEmbedBuilder extends EmbedBuilder {
final imageUrl = standardizeImageUrl(node.value.data);
OptionalSize? imageSize;
final style = node.style.attributes['style'];
if (base.isMobile() && style != null) {
final attrs = base.parseKeyValuePairs(style.value.toString(), {
Attribute.mobileWidth,
Attribute.mobileHeight,
Attribute.mobileMargin,
Attribute.mobileAlignment
});
// TODO: Please use the one from [Attribute]
const marginKey = 'margin';
const alignmentKey = 'alignment';
if (style != null) {
final attrs = base.isMobile()
? base.parseKeyValuePairs(style.value.toString(), {
Attribute.mobileWidth,
Attribute.mobileHeight,
Attribute.mobileMargin,
Attribute.mobileAlignment,
})
: base.parseKeyValuePairs(style.value.toString(), {
Attribute.width.key,
Attribute.height.key,
marginKey,
alignmentKey,
});
if (attrs.isNotEmpty) {
final width = double.tryParse(
(base.isMobile()
? attrs[Attribute.mobileWidth]
: attrs[Attribute.width.key]) ??
'',
);
final height = double.tryParse(
(base.isMobile()
? attrs[Attribute.mobileHeight]
: attrs[Attribute.height.key]) ??
'',
);
final alignment = base.getAlignment(base.isMobile()
? attrs[Attribute.mobileAlignment]
: attrs[alignmentKey]);
final margin = (base.isMobile()
? double.tryParse(Attribute.mobileMargin)
: double.tryParse(marginKey)) ??
0.0;
assert(
attrs[Attribute.mobileWidth] != null &&
attrs[Attribute.mobileHeight] != null,
'mobileWidth and mobileHeight must be specified');
final w = double.parse(attrs[Attribute.mobileWidth]!);
final h = double.parse(attrs[Attribute.mobileHeight]!);
imageSize = OptionalSize(w, h);
final m = attrs[Attribute.mobileMargin] == null
? 0.0
: double.parse(attrs[Attribute.mobileMargin]!);
final a = base.getAlignment(attrs[Attribute.mobileAlignment]);
width != null && height != null,
base.isMobile()
? 'mobileWidth and mobileHeight must be specified'
: 'width and height must be specified',
);
imageSize = OptionalSize(width, height);
image = Padding(
padding: EdgeInsets.all(m),
child: imageByUrl(
imageUrl,
width: w,
height: h,
alignment: a,
imageProviderBuilder: imageProviderBuilder,
imageErrorWidgetBuilder: imageErrorWidgetBuilder,
));
padding: EdgeInsets.all(margin),
child: getQuillImageByUrl(
imageUrl,
width: width,
height: height,
alignment: alignment,
imageProviderBuilder: imageProviderBuilder,
imageErrorWidgetBuilder: imageErrorWidgetBuilder,
),
);
}
}
if (imageSize == null) {
image = imageByUrl(
image = getQuillImageByUrl(
imageUrl,
imageProviderBuilder: imageProviderBuilder,
imageErrorWidgetBuilder: imageErrorWidgetBuilder,
@ -108,26 +137,89 @@ class ImageEmbedBuilder extends EmbedBuilder {
onPressed: () {
Navigator.pop(context);
showCupertinoModalPopup<void>(
context: context,
builder: (context) {
final screenSize = MediaQuery.of(context).size;
return ImageResizer(
onImageResize: (w, h) {
final res = getEmbedNode(
controller, controller.selection.start);
final attr = base.replaceStyleString(
getImageStyleString(controller), w, h);
controller
..skipRequestKeyboard = true
..formatText(
res.offset, 1, StyleAttribute(attr));
},
imageWidth: imageSize?.width,
imageHeight: imageSize?.height,
maxWidth: screenSize.width,
maxHeight: screenSize.height,
);
});
context: context,
builder: (context) {
// This now will rebuilt only when there are changes
// to the size only and not other properties
// to reduce the builts and improve peformance
final screenSize = MediaQuery.sizeOf(context);
return ImageResizer(
onImageResize: (w, h) {
print('Width = $w, Height = $h');
final res = getEmbedNode(
controller,
controller.selection.start,
);
// For desktop
String _replaceStyleStringWithSize(
String s,
double width,
double height,
) {
final result = <String, String>{};
final pairs = s.split(';');
for (final pair in pairs) {
final _index = pair.indexOf(':');
if (_index < 0) {
continue;
}
final _key = pair.substring(0, _index).trim();
result[_key] =
pair.substring(_index + 1).trim();
}
result[Attribute.width.key] = width.toString();
result[Attribute.height.key] = height.toString();
final sb = StringBuffer();
for (final pair in result.entries) {
sb
..write(pair.key)
..write(': ')
..write(pair.value)
..write('; ');
}
return sb.toString();
}
// TODO: Please consider add bool property in
// replaceStyleString that will use either
// mobileWidth or width based on that property
// but that require changes to the flutter_quill
// and it should be published and then we can
// change it from flutter_quill_extensions
final attr = base.isMobile()
? base.replaceStyleString(
getImageStyleString(controller),
w,
h,
)
: _replaceStyleStringWithSize(
getImageStyleString(controller),
w,
h,
);
controller
..skipRequestKeyboard = true
..formatText(
res.offset,
1,
StyleAttribute(attr),
);
print(
jsonEncode(
controller.document.toDelta().toJson(),
),
);
},
imageWidth: imageSize?.width,
imageHeight: imageSize?.height,
maxWidth: screenSize.width,
maxHeight: screenSize.height,
);
},
);
},
);
final copyOption = _SimpleDialogItem(
@ -182,7 +274,7 @@ class ImageEmbedBuilder extends EmbedBuilder {
),
),
children: [
if (base.isMobile()) resizeOption,
resizeOption,
copyOption,
removeOption,
]),
@ -332,76 +424,81 @@ Widget _menuOptionsForReadonlyImage({
return GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) {
final saveOption = _SimpleDialogItem(
icon: Icons.save,
color: Colors.greenAccent,
text: 'Save'.i18n,
onPressed: () async {
imageUrl = appendFileExtensionToImageUrl(imageUrl);
final messenger = ScaffoldMessenger.of(context);
Navigator.of(context).pop();
final saveImageResult = await saveImage(imageUrl);
final imageSavedSuccessfully = saveImageResult.isSuccess;
messenger.clearSnackBars();
if (!imageSavedSuccessfully) {
messenger.showSnackBar(SnackBar(
content: Text(
'Error while saving image'.i18n,
)));
return;
}
var message;
switch (saveImageResult.method) {
case SaveImageResultMethod.network:
message = 'Saved using the network'.i18n;
break;
case SaveImageResultMethod.localStorage:
message = 'Saved using the local storage'.i18n;
break;
}
messenger.showSnackBar(
SnackBar(
content: Text(message),
),
);
},
);
final zoomOption = _SimpleDialogItem(
icon: Icons.zoom_in,
color: Colors.cyanAccent,
text: 'Zoom'.i18n,
onPressed: () {
Navigator.pushReplacement(
context,
// TODO: Consider add support for other theme system
// like Cupertino or at least add the option to by
// by using PageRoute as option so dev can ovveride this
// this change should be done in all places if you want to
MaterialPageRoute(
builder: (context) => ImageTapWrapper(
imageUrl: imageUrl,
imageProviderBuilder: imageProviderBuilder,
imageErrorWidgetBuilder: imageErrorWidgetBuilder,
),
context: context,
builder: (context) {
final saveOption = _SimpleDialogItem(
icon: Icons.save,
color: Colors.greenAccent,
text: 'Save'.i18n,
onPressed: () async {
imageUrl = appendFileExtensionToImageUrl(imageUrl);
final messenger = ScaffoldMessenger.of(context);
Navigator.of(context).pop();
final saveImageResult = await saveImage(imageUrl);
final imageSavedSuccessfully = saveImageResult.isSuccess;
messenger.clearSnackBars();
if (!imageSavedSuccessfully) {
messenger.showSnackBar(SnackBar(
content: Text(
'Error while saving image'.i18n,
)));
return;
}
var message;
switch (saveImageResult.method) {
case SaveImageResultMethod.network:
message = 'Saved using the network'.i18n;
break;
case SaveImageResultMethod.localStorage:
message = 'Saved using the local storage'.i18n;
break;
}
messenger.showSnackBar(
SnackBar(
content: Text(message),
),
);
},
);
final zoomOption = _SimpleDialogItem(
icon: Icons.zoom_in,
color: Colors.cyanAccent,
text: 'Zoom'.i18n,
onPressed: () {
Navigator.pushReplacement(
context,
// TODO: Consider add support for other theme system
// like Cupertino or at least add the option to by
// by using PageRoute as option so dev can ovveride this
// this change should be done in all places if you want to
MaterialPageRoute(
builder: (context) => ImageTapWrapper(
imageUrl: imageUrl,
imageProviderBuilder: imageProviderBuilder,
imageErrorWidgetBuilder: imageErrorWidgetBuilder,
),
);
},
);
return Padding(
padding: const EdgeInsets.fromLTRB(50, 0, 50, 0),
child: SimpleDialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
children: [saveOption, zoomOption]),
);
});
),
);
},
);
return Padding(
padding: const EdgeInsets.fromLTRB(50, 0, 50, 0),
child: SimpleDialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
children: [saveOption, zoomOption],
),
);
},
);
},
child: image);
}

@ -224,9 +224,8 @@ class _MediaLinkDialogState extends State<MediaLinkDialog> {
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;
final size = MediaQuery.sizeOf(context);
final maxWidth = kIsWeb ? size.width / 4 : size.width - 80;
return BoxConstraints(maxWidth: maxWidth, maxHeight: 80);
}();
@ -338,13 +337,13 @@ class MediaSourceSelectorDialog extends StatelessWidget {
Widget build(BuildContext context) {
final constraints = dialogTheme?.mediaSelectorDialogConstraints ??
() {
final mediaQuery = MediaQuery.of(context);
final size = MediaQuery.sizeOf(context);
double maxWidth, maxHeight;
if (kIsWeb) {
maxWidth = mediaQuery.size.width / 7;
maxHeight = mediaQuery.size.height / 7;
maxWidth = size.width / 7;
maxHeight = size.height / 7;
} else {
maxWidth = mediaQuery.size.width - 80;
maxWidth = size.width - 80;
maxHeight = maxWidth / 2;
}
return BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight);

@ -28,7 +28,7 @@ String getImageStyleString(QuillController controller) {
return s ?? '';
}
Image imageByUrl(
Image getQuillImageByUrl(
String imageUrl, {
required ImageEmbedBuilderProviderBuilder? imageProviderBuilder,
required ImageErrorWidgetBuilder? imageErrorWidgetBuilder,
@ -124,7 +124,7 @@ class ImageTapWrapper extends StatelessWidget {
return Scaffold(
body: Container(
constraints: BoxConstraints.expand(
height: MediaQuery.of(context).size.height,
height: MediaQuery.sizeOf(context).height,
),
child: Stack(
children: [
@ -145,7 +145,7 @@ class ImageTapWrapper extends StatelessWidget {
),
Positioned(
right: 10,
top: MediaQuery.of(context).padding.top + 10.0,
top: MediaQuery.paddingOf(context).top + 10.0,
child: InkWell(
onTap: () {
Navigator.pop(context);

@ -42,6 +42,11 @@ class _ImageResizerState extends State<ImageResizer> {
return _showCupertinoMenu();
case TargetPlatform.android:
return _showMaterialMenu();
case TargetPlatform.macOS:
case TargetPlatform.windows:
case TargetPlatform.linux:
case TargetPlatform.fuchsia:
return _showMaterialMenu();
default:
throw 'Not supposed to be invoked for $defaultTargetPlatform';
}
@ -68,7 +73,11 @@ class _ImageResizerState extends State<ImageResizer> {
}
Widget _slider(
double value, double max, String label, ValueChanged<double> onChanged) {
double value,
double max,
String label,
ValueChanged<double> onChanged,
) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Card(

@ -110,6 +110,12 @@ class Attribute<T> {
static const String mobileAlignment = 'mobileAlignment';
/// For other platforms, for mobile use [mobileAlignment]
static const String alignment = 'alignment';
/// For other platforms, for mobile use [mobileMargin]
static const String margin = 'margin';
static const ImageAttribute image = ImageAttribute(null);
static const VideoAttribute video = VideoAttribute(null);

@ -59,8 +59,14 @@ class Rules {
_customRules = customRules;
}
Delta apply(RuleType ruleType, Document document, int index,
{int? len, Object? data, Attribute? attribute}) {
Delta apply(
RuleType ruleType,
Document document,
int index, {
int? len,
Object? data,
Attribute? attribute,
}) {
final delta = document.toDelta();
for (final rule in _customRules + _rules) {
if (rule.type != ruleType) {
@ -76,6 +82,8 @@ class Rules {
rethrow;
}
}
throw 'Apply rules failed';
throw FormatException(
'Apply delta rules failed. No matching rule found for type: $ruleType',
);
}
}

@ -1,12 +1,15 @@
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/foundation.dart'
show kIsWeb, TargetPlatform, defaultTargetPlatform;
bool isMobile([TargetPlatform? targetPlatform]) {
if (kIsWeb) return false;
targetPlatform ??= defaultTargetPlatform;
return {TargetPlatform.iOS, TargetPlatform.android}.contains(targetPlatform);
}
bool isDesktop([TargetPlatform? targetPlatform]) {
if (kIsWeb) return false;
targetPlatform ??= defaultTargetPlatform;
return {TargetPlatform.macOS, TargetPlatform.linux, TargetPlatform.windows}
.contains(targetPlatform);
@ -18,6 +21,7 @@ bool isKeyboardOS([TargetPlatform? targetPlatform]) {
}
bool isAppleOS([TargetPlatform? targetPlatform]) {
if (kIsWeb) return false;
targetPlatform ??= defaultTargetPlatform;
return {
TargetPlatform.macOS,

@ -497,7 +497,7 @@ class QuillEditorState extends State<QuillEditor>
cupertinoTheme.primaryColor.withOpacity(0.40);
cursorRadius ??= const Radius.circular(2);
cursorOffset = Offset(
iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0);
iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0);
} else {
textSelectionControls = materialTextSelectionControls;
paintCursorAboveText = false;

@ -974,7 +974,7 @@ class RawEditorState extends EditorState
widget.selectionColor,
widget.enableInteractiveSelection,
_hasFocus,
MediaQuery.of(context).devicePixelRatio,
MediaQuery.devicePixelRatioOf(context),
_cursorCont);
return editableTextLine;
}

@ -159,7 +159,7 @@ class EditableTextBlock extends StatelessWidget {
color,
enableInteractiveSelection,
hasFocus,
MediaQuery.of(context).devicePixelRatio,
MediaQuery.devicePixelRatioOf(context),
cursorCont);
final nodeTextDirection = getDirectionOfNode(line);
children.add(Directionality(

@ -245,9 +245,8 @@ class _LinkStyleDialogState extends State<LinkStyleDialog> {
final constraints = widget.constraints ??
widget.dialogTheme?.linkDialogConstraints ??
() {
final mediaQuery = MediaQuery.of(context);
final maxWidth =
kIsWeb ? mediaQuery.size.width / 4 : mediaQuery.size.width - 80;
final size = MediaQuery.sizeOf(context);
final maxWidth = kIsWeb ? size.width / 4 : size.width - 80;
return BoxConstraints(maxWidth: maxWidth, maxHeight: 80);
}();

Loading…
Cancel
Save