Text Alignment functions + Block Format standards (#382)

* Mute problem reports

* For PR: Text Alignment functions + Block Format standards

* Restore analysis_options.yaml to original settings
pull/410/head
Aldy J 4 years ago committed by GitHub
parent 8945379379
commit 97ba9cfb39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      example/lib/pages/home_page.dart
  2. 2
      example/macos/Runner/DebugProfile.entitlements
  3. 6
      example/windows/flutter/generated_plugin_registrant.cc
  4. 7
      lib/src/models/documents/attribute.dart
  5. 17
      lib/src/models/documents/nodes/line.dart
  6. 29
      lib/src/models/rules/format.dart
  7. 6
      lib/src/widgets/raw_editor.dart
  8. 6
      lib/src/widgets/simple_viewer.dart
  9. 7
      lib/src/widgets/text_block.dart
  10. 15
      lib/src/widgets/text_line.dart
  11. 34
      lib/src/widgets/toolbar.dart
  12. 129
      lib/src/widgets/toolbar/select_alignment_button.dart
  13. 6
      lib/src/widgets/toolbar/toggle_check_list_button.dart
  14. 6
      lib/src/widgets/toolbar/toggle_style_button.dart

@ -149,18 +149,23 @@ class _HomePageState extends State<HomePage> {
onVideoPickCallback: _onVideoPickCallback, onVideoPickCallback: _onVideoPickCallback,
// uncomment to provide a custom "pick from" dialog. // uncomment to provide a custom "pick from" dialog.
// mediaPickSettingSelector: _selectMediaPickSetting, // mediaPickSettingSelector: _selectMediaPickSetting,
showAlignmentButtons: true,
); );
if (kIsWeb) { if (kIsWeb) {
toolbar = QuillToolbar.basic( toolbar = QuillToolbar.basic(
controller: _controller!, controller: _controller!,
onImagePickCallback: _onImagePickCallback, onImagePickCallback: _onImagePickCallback,
webImagePickImpl: _webImagePickImpl); webImagePickImpl: _webImagePickImpl,
showAlignmentButtons: true,
);
} }
if (_isDesktop()) { if (_isDesktop()) {
toolbar = QuillToolbar.basic( toolbar = QuillToolbar.basic(
controller: _controller!, controller: _controller!,
onImagePickCallback: _onImagePickCallback, onImagePickCallback: _onImagePickCallback,
filePickImpl: openFileSystemPickerForDesktop); filePickImpl: openFileSystemPickerForDesktop,
showAlignmentButtons: true,
);
} }
return SafeArea( return SafeArea(

@ -8,5 +8,7 @@
<true/> <true/>
<key>com.apple.security.network.server</key> <key>com.apple.security.network.server</key>
<true/> <true/>
<key>com.apple.security.network.client</key>
<true/>
</dict> </dict>
</plist> </plist>

@ -4,9 +4,9 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <url_launcher_windows/url_launcher_plugin.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
UrlLauncherPluginRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherPlugin")); registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }

@ -111,6 +111,13 @@ class Attribute<T> {
Attribute.indent.key, Attribute.indent.key,
}); });
static final Set<String> exclusiveBlockKeys = LinkedHashSet.of({
Attribute.header.key,
Attribute.list.key,
Attribute.codeBlock.key,
Attribute.blockQuote.key,
});
static Attribute<int?> get h1 => HeaderAttribute(level: 1); static Attribute<int?> get h1 => HeaderAttribute(level: 1);
static Attribute<int?> get h2 => HeaderAttribute(level: 2); static Attribute<int?> get h2 => HeaderAttribute(level: 2);

@ -202,11 +202,26 @@ class Line extends Container<Leaf?> {
if (parent is Block) { if (parent is Block) {
final parentStyle = (parent as Block).style.getBlocksExceptHeader(); final parentStyle = (parent as Block).style.getBlocksExceptHeader();
if (blockStyle.value == null) { // Ensure that we're only unwrapping the block only if we unset a single
// block format in the `parentStyle` and there are no more block formats
// left to unset.
if (blockStyle.value == null &&
parentStyle.containsKey(blockStyle.key) &&
parentStyle.length == 1) {
_unwrap(); _unwrap();
} else if (!const MapEquality() } else if (!const MapEquality()
.equals(newStyle.getBlocksExceptHeader(), parentStyle)) { .equals(newStyle.getBlocksExceptHeader(), parentStyle)) {
_unwrap(); _unwrap();
// Block style now can contain multiple attributes
if (newStyle.attributes.keys
.any(Attribute.exclusiveBlockKeys.contains)) {
parentStyle.removeWhere(
(key, attr) => Attribute.exclusiveBlockKeys.contains(key));
}
parentStyle.removeWhere(
(key, attr) => newStyle?.attributes.keys.contains(key) ?? false);
final parentStyleToMerge = Style.attr(parentStyle);
newStyle = newStyle.mergeAll(parentStyleToMerge);
_applyBlockStyles(newStyle); _applyBlockStyles(newStyle);
} // else the same style, no-op. } // else the same style, no-op.
} else if (blockStyle.value != null) { } else if (blockStyle.value != null) {

@ -39,10 +39,23 @@ class ResolveLineFormatRule extends FormatRule {
final tmp = Delta(); final tmp = Delta();
var offset = 0; var offset = 0;
// Enforce Block Format exclusivity by rule
final removedBlocks = Attribute.exclusiveBlockKeys.contains(attribute.key)
? op.attributes?.keys
.where((key) =>
Attribute.exclusiveBlockKeys.contains(key) &&
attribute.key != key &&
attribute.value != null)
.map((key) => MapEntry<String, dynamic>(key, null)) ??
[]
: <MapEntry<String, dynamic>>[];
for (var lineBreak = text.indexOf('\n'); for (var lineBreak = text.indexOf('\n');
lineBreak >= 0; lineBreak >= 0;
lineBreak = text.indexOf('\n', offset)) { lineBreak = text.indexOf('\n', offset)) {
tmp..retain(lineBreak - offset)..retain(1, attribute.toJson()); tmp
..retain(lineBreak - offset)
..retain(1, attribute.toJson()..addEntries(removedBlocks));
offset = lineBreak + 1; offset = lineBreak + 1;
} }
tmp.retain(text.length - offset); tmp.retain(text.length - offset);
@ -57,7 +70,19 @@ class ResolveLineFormatRule extends FormatRule {
delta.retain(op.length!); delta.retain(op.length!);
continue; continue;
} }
delta..retain(lineBreak)..retain(1, attribute.toJson()); // Enforce Block Format exclusivity by rule
final removedBlocks = Attribute.exclusiveBlockKeys.contains(attribute.key)
? op.attributes?.keys
.where((key) =>
Attribute.exclusiveBlockKeys.contains(key) &&
attribute.key != key &&
attribute.value != null)
.map((key) => MapEntry<String, dynamic>(key, null)) ??
[]
: <MapEntry<String, dynamic>>[];
delta
..retain(lineBreak)
..retain(1, attribute.toJson()..addEntries(removedBlocks));
break; break;
} }
return delta; return delta;

@ -313,8 +313,12 @@ class RawEditorState extends EditorState
return defaultStyles!.code!.verticalSpacing; return defaultStyles!.code!.verticalSpacing;
} else if (attrs.containsKey(Attribute.indent.key)) { } else if (attrs.containsKey(Attribute.indent.key)) {
return defaultStyles!.indent!.verticalSpacing; return defaultStyles!.indent!.verticalSpacing;
} else if (attrs.containsKey(Attribute.list.key)) {
return defaultStyles!.lists!.verticalSpacing;
} else if (attrs.containsKey(Attribute.align.key)) {
return defaultStyles!.align!.verticalSpacing;
} }
return defaultStyles!.lists!.verticalSpacing; return const Tuple2(0, 0);
} }
@override @override

@ -295,8 +295,12 @@ class _QuillSimpleViewerState extends State<QuillSimpleViewer>
return defaultStyles!.code!.verticalSpacing; return defaultStyles!.code!.verticalSpacing;
} else if (attrs.containsKey(Attribute.indent.key)) { } else if (attrs.containsKey(Attribute.indent.key)) {
return defaultStyles!.indent!.verticalSpacing; return defaultStyles!.indent!.verticalSpacing;
} else if (attrs.containsKey(Attribute.list.key)) {
return defaultStyles!.lists!.verticalSpacing;
} else if (attrs.containsKey(Attribute.align.key)) {
return defaultStyles!.align!.verticalSpacing;
} }
return defaultStyles!.lists!.verticalSpacing; return const Tuple2(0, 0);
} }
void _nullSelectionChanged( void _nullSelectionChanged(

@ -213,11 +213,16 @@ class EditableTextBlock extends StatelessWidget {
extraIndent = 16.0 * indent.value; extraIndent = 16.0 * indent.value;
} }
var baseIndent = 0.0;
if (attrs.containsKey(Attribute.blockQuote.key)) { if (attrs.containsKey(Attribute.blockQuote.key)) {
return 16.0 + extraIndent; return 16.0 + extraIndent;
} else if (attrs.containsKey(Attribute.list.key) ||
attrs.containsKey(Attribute.codeBlock.key)) {
baseIndent = 32.0;
} }
return 32.0 + extraIndent; return baseIndent + extraIndent;
} }
Tuple2 _getSpacingForLine( Tuple2 _getSpacingForLine(

@ -104,11 +104,11 @@ class TextLine extends StatelessWidget {
TextAlign _getTextAlign() { TextAlign _getTextAlign() {
final alignment = line.style.attributes[Attribute.align.key]; final alignment = line.style.attributes[Attribute.align.key];
if (alignment == Attribute.leftAlignment) { if (alignment == Attribute.leftAlignment) {
return TextAlign.left; return TextAlign.start;
} else if (alignment == Attribute.centerAlignment) { } else if (alignment == Attribute.centerAlignment) {
return TextAlign.center; return TextAlign.center;
} else if (alignment == Attribute.rightAlignment) { } else if (alignment == Attribute.rightAlignment) {
return TextAlign.right; return TextAlign.end;
} else if (alignment == Attribute.justifyAlignment) { } else if (alignment == Attribute.justifyAlignment) {
return TextAlign.justify; return TextAlign.justify;
} }
@ -140,13 +140,20 @@ class TextLine extends StatelessWidget {
textStyle = textStyle.merge(m[header] ?? defaultStyles.paragraph!.style); textStyle = textStyle.merge(m[header] ?? defaultStyles.paragraph!.style);
final block = line.style.getBlockExceptHeader(); // Only retrieve exclusive block format for the line style purpose
Attribute? block;
line.style.getBlocksExceptHeader().forEach((key, value) {
if (Attribute.exclusiveBlockKeys.contains(key)) {
block = value;
}
});
TextStyle? toMerge; TextStyle? toMerge;
if (block == Attribute.blockQuote) { if (block == Attribute.blockQuote) {
toMerge = defaultStyles.quote!.style; toMerge = defaultStyles.quote!.style;
} else if (block == Attribute.codeBlock) { } else if (block == Attribute.codeBlock) {
toMerge = defaultStyles.code!.style; toMerge = defaultStyles.code!.style;
} else if (block != null) { } else if (block == Attribute.list) {
toMerge = defaultStyles.lists!.style; toMerge = defaultStyles.lists!.style;
} }

@ -14,6 +14,7 @@ import 'toolbar/image_button.dart';
import 'toolbar/indent_button.dart'; import 'toolbar/indent_button.dart';
import 'toolbar/insert_embed_button.dart'; import 'toolbar/insert_embed_button.dart';
import 'toolbar/link_style_button.dart'; import 'toolbar/link_style_button.dart';
import 'toolbar/select_alignment_button.dart';
import 'toolbar/select_header_style_button.dart'; import 'toolbar/select_header_style_button.dart';
import 'toolbar/toggle_check_list_button.dart'; import 'toolbar/toggle_check_list_button.dart';
import 'toolbar/toggle_style_button.dart'; import 'toolbar/toggle_style_button.dart';
@ -29,6 +30,7 @@ export 'toolbar/insert_embed_button.dart';
export 'toolbar/link_style_button.dart'; export 'toolbar/link_style_button.dart';
export 'toolbar/quill_dropdown_button.dart'; export 'toolbar/quill_dropdown_button.dart';
export 'toolbar/quill_icon_button.dart'; export 'toolbar/quill_icon_button.dart';
export 'toolbar/select_alignment_button.dart';
export 'toolbar/select_header_style_button.dart'; export 'toolbar/select_header_style_button.dart';
export 'toolbar/toggle_check_list_button.dart'; export 'toolbar/toggle_check_list_button.dart';
export 'toolbar/toggle_style_button.dart'; export 'toolbar/toggle_style_button.dart';
@ -71,6 +73,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
bool showColorButton = true, bool showColorButton = true,
bool showBackgroundColorButton = true, bool showBackgroundColorButton = true,
bool showClearFormat = true, bool showClearFormat = true,
bool showAlignmentButtons = false,
bool showHeaderStyle = true, bool showHeaderStyle = true,
bool showListNumbers = true, bool showListNumbers = true,
bool showListBullets = true, bool showListBullets = true,
@ -105,6 +108,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
showClearFormat || showClearFormat ||
onImagePickCallback != null || onImagePickCallback != null ||
onVideoPickCallback != null, onVideoPickCallback != null,
showAlignmentButtons,
showHeaderStyle, showHeaderStyle,
showListNumbers || showListBullets || showListCheck || showCodeBlock, showListNumbers || showListBullets || showListCheck || showCodeBlock,
showQuote || showIndent, showQuote || showIndent,
@ -220,21 +224,37 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
(isButtonGroupShown[1] || (isButtonGroupShown[1] ||
isButtonGroupShown[2] || isButtonGroupShown[2] ||
isButtonGroupShown[3] || isButtonGroupShown[3] ||
isButtonGroupShown[4])) isButtonGroupShown[4] ||
isButtonGroupShown[5]))
VerticalDivider( VerticalDivider(
indent: 12, indent: 12,
endIndent: 12, endIndent: 12,
color: Colors.grey.shade400, color: Colors.grey.shade400,
), ),
if (showHeaderStyle) if (showAlignmentButtons)
SelectHeaderStyleButton( SelectAlignmentButton(
controller: controller, controller: controller,
iconSize: toolbarIconSize, iconSize: toolbarIconSize,
), ),
if (isButtonGroupShown[1] && if (isButtonGroupShown[1] &&
(isButtonGroupShown[2] || (isButtonGroupShown[2] ||
isButtonGroupShown[3] || isButtonGroupShown[3] ||
isButtonGroupShown[4])) isButtonGroupShown[4] ||
isButtonGroupShown[5]))
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showHeaderStyle)
SelectHeaderStyleButton(
controller: controller,
iconSize: toolbarIconSize,
),
if (isButtonGroupShown[2] &&
(isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
VerticalDivider( VerticalDivider(
indent: 12, indent: 12,
endIndent: 12, endIndent: 12,
@ -268,8 +288,8 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
icon: Icons.code, icon: Icons.code,
iconSize: toolbarIconSize, iconSize: toolbarIconSize,
), ),
if (isButtonGroupShown[2] && if (isButtonGroupShown[3] &&
(isButtonGroupShown[3] || isButtonGroupShown[4])) (isButtonGroupShown[4] || isButtonGroupShown[5]))
VerticalDivider( VerticalDivider(
indent: 12, indent: 12,
endIndent: 12, endIndent: 12,
@ -296,7 +316,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
controller: controller, controller: controller,
isIncrease: false, isIncrease: false,
), ),
if (isButtonGroupShown[3] && isButtonGroupShown[4]) if (isButtonGroupShown[4] && isButtonGroupShown[5])
VerticalDivider( VerticalDivider(
indent: 12, indent: 12,
endIndent: 12, endIndent: 12,

@ -0,0 +1,129 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../models/documents/attribute.dart';
import '../../models/documents/style.dart';
import '../controller.dart';
import '../toolbar.dart';
class SelectAlignmentButton extends StatefulWidget {
const SelectAlignmentButton({
required this.controller,
this.iconSize = kDefaultIconSize,
Key? key,
}) : super(key: key);
final QuillController controller;
final double iconSize;
@override
_SelectAlignmentButtonState createState() => _SelectAlignmentButtonState();
}
class _SelectAlignmentButtonState extends State<SelectAlignmentButton> {
Attribute? _value;
Style get _selectionStyle => widget.controller.getSelectionStyle();
@override
void initState() {
super.initState();
setState(() {
_value = _selectionStyle.attributes[Attribute.align.key] ??
Attribute.leftAlignment;
});
widget.controller.addListener(_didChangeEditingValue);
}
@override
Widget build(BuildContext context) {
final _valueToText = <Attribute, String>{
Attribute.leftAlignment: Attribute.leftAlignment.value!,
Attribute.centerAlignment: Attribute.centerAlignment.value!,
Attribute.rightAlignment: Attribute.rightAlignment.value!,
Attribute.justifyAlignment: Attribute.justifyAlignment.value!,
};
final _valueAttribute = <Attribute>[
Attribute.leftAlignment,
Attribute.centerAlignment,
Attribute.rightAlignment,
Attribute.justifyAlignment
];
final _valueString = <String>[
Attribute.leftAlignment.value!,
Attribute.centerAlignment.value!,
Attribute.rightAlignment.value!,
Attribute.justifyAlignment.value!,
];
final theme = Theme.of(context);
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(4, (index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0),
child: ConstrainedBox(
constraints: BoxConstraints.tightFor(
width: widget.iconSize * kIconButtonFactor,
height: widget.iconSize * kIconButtonFactor,
),
child: RawMaterialButton(
hoverElevation: 0,
highlightElevation: 0,
elevation: 0,
visualDensity: VisualDensity.compact,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(2)),
fillColor: _valueToText[_value] == _valueString[index]
? theme.toggleableActiveColor
: theme.canvasColor,
onPressed: () => _valueAttribute[index] == Attribute.leftAlignment
? widget.controller
.formatSelection(Attribute.clone(Attribute.align, null))
: widget.controller.formatSelection(_valueAttribute[index]),
child: Icon(
_valueString[index] == Attribute.leftAlignment.value
? Icons.format_align_left
: _valueString[index] == Attribute.centerAlignment.value
? Icons.format_align_center
: _valueString[index] == Attribute.rightAlignment.value
? Icons.format_align_right
: Icons.format_align_justify,
size: widget.iconSize,
color: _valueToText[_value] == _valueString[index]
? theme.primaryIconTheme.color
: theme.iconTheme.color,
),
),
),
);
}),
);
}
void _didChangeEditingValue() {
setState(() {
_value = _selectionStyle.attributes[Attribute.align.key] ??
Attribute.leftAlignment;
});
}
@override
void didUpdateWidget(covariant SelectAlignmentButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != widget.controller) {
oldWidget.controller.removeListener(_didChangeEditingValue);
widget.controller.addListener(_didChangeEditingValue);
_value = _selectionStyle.attributes[Attribute.align.key] ??
Attribute.leftAlignment;
}
}
@override
void dispose() {
widget.controller.removeListener(_didChangeEditingValue);
super.dispose();
}
}

@ -81,17 +81,13 @@ class _ToggleCheckListButtonState extends State<ToggleCheckListButton> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isInCodeBlock =
_selectionStyle.attributes.containsKey(Attribute.codeBlock.key);
final isEnabled =
!isInCodeBlock || Attribute.list.key == Attribute.codeBlock.key;
return widget.childBuilder( return widget.childBuilder(
context, context,
Attribute.unchecked, Attribute.unchecked,
widget.icon, widget.icon,
widget.fillColor, widget.fillColor,
_isToggled, _isToggled,
isEnabled ? _toggleAttribute : null, _toggleAttribute,
widget.iconSize, widget.iconSize,
); );
} }

@ -56,17 +56,13 @@ class _ToggleStyleButtonState extends State<ToggleStyleButton> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isInCodeBlock =
_selectionStyle.attributes.containsKey(Attribute.codeBlock.key);
final isEnabled =
!isInCodeBlock || widget.attribute.key == Attribute.codeBlock.key;
return widget.childBuilder( return widget.childBuilder(
context, context,
widget.attribute, widget.attribute,
widget.icon, widget.icon,
widget.fillColor, widget.fillColor,
_isToggled, _isToggled,
isEnabled ? _toggleAttribute : null, _toggleAttribute,
widget.iconSize, widget.iconSize,
); );
} }

Loading…
Cancel
Save