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,
// uncomment to provide a custom "pick from" dialog.
// mediaPickSettingSelector: _selectMediaPickSetting,
showAlignmentButtons: true,
);
if (kIsWeb) {
toolbar = QuillToolbar.basic(
controller: _controller!,
onImagePickCallback: _onImagePickCallback,
webImagePickImpl: _webImagePickImpl);
controller: _controller!,
onImagePickCallback: _onImagePickCallback,
webImagePickImpl: _webImagePickImpl,
showAlignmentButtons: true,
);
}
if (_isDesktop()) {
toolbar = QuillToolbar.basic(
controller: _controller!,
onImagePickCallback: _onImagePickCallback,
filePickImpl: openFileSystemPickerForDesktop);
controller: _controller!,
onImagePickCallback: _onImagePickCallback,
filePickImpl: openFileSystemPickerForDesktop,
showAlignmentButtons: true,
);
}
return SafeArea(

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

@ -4,9 +4,9 @@
#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) {
UrlLauncherPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

@ -111,6 +111,13 @@ class Attribute<T> {
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 h2 => HeaderAttribute(level: 2);

@ -202,11 +202,26 @@ class Line extends Container<Leaf?> {
if (parent is Block) {
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();
} else if (!const MapEquality()
.equals(newStyle.getBlocksExceptHeader(), parentStyle)) {
_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);
} // else the same style, no-op.
} else if (blockStyle.value != null) {

@ -39,10 +39,23 @@ class ResolveLineFormatRule extends FormatRule {
final tmp = Delta();
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');
lineBreak >= 0;
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;
}
tmp.retain(text.length - offset);
@ -57,7 +70,19 @@ class ResolveLineFormatRule extends FormatRule {
delta.retain(op.length!);
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;
}
return delta;

@ -313,8 +313,12 @@ class RawEditorState extends EditorState
return defaultStyles!.code!.verticalSpacing;
} else if (attrs.containsKey(Attribute.indent.key)) {
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

@ -295,8 +295,12 @@ class _QuillSimpleViewerState extends State<QuillSimpleViewer>
return defaultStyles!.code!.verticalSpacing;
} else if (attrs.containsKey(Attribute.indent.key)) {
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(

@ -213,11 +213,16 @@ class EditableTextBlock extends StatelessWidget {
extraIndent = 16.0 * indent.value;
}
var baseIndent = 0.0;
if (attrs.containsKey(Attribute.blockQuote.key)) {
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(

@ -104,11 +104,11 @@ class TextLine extends StatelessWidget {
TextAlign _getTextAlign() {
final alignment = line.style.attributes[Attribute.align.key];
if (alignment == Attribute.leftAlignment) {
return TextAlign.left;
return TextAlign.start;
} else if (alignment == Attribute.centerAlignment) {
return TextAlign.center;
} else if (alignment == Attribute.rightAlignment) {
return TextAlign.right;
return TextAlign.end;
} else if (alignment == Attribute.justifyAlignment) {
return TextAlign.justify;
}
@ -140,13 +140,20 @@ class TextLine extends StatelessWidget {
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;
if (block == Attribute.blockQuote) {
toMerge = defaultStyles.quote!.style;
} else if (block == Attribute.codeBlock) {
toMerge = defaultStyles.code!.style;
} else if (block != null) {
} else if (block == Attribute.list) {
toMerge = defaultStyles.lists!.style;
}

@ -14,6 +14,7 @@ import 'toolbar/image_button.dart';
import 'toolbar/indent_button.dart';
import 'toolbar/insert_embed_button.dart';
import 'toolbar/link_style_button.dart';
import 'toolbar/select_alignment_button.dart';
import 'toolbar/select_header_style_button.dart';
import 'toolbar/toggle_check_list_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/quill_dropdown_button.dart';
export 'toolbar/quill_icon_button.dart';
export 'toolbar/select_alignment_button.dart';
export 'toolbar/select_header_style_button.dart';
export 'toolbar/toggle_check_list_button.dart';
export 'toolbar/toggle_style_button.dart';
@ -71,6 +73,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
bool showColorButton = true,
bool showBackgroundColorButton = true,
bool showClearFormat = true,
bool showAlignmentButtons = false,
bool showHeaderStyle = true,
bool showListNumbers = true,
bool showListBullets = true,
@ -105,6 +108,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
showClearFormat ||
onImagePickCallback != null ||
onVideoPickCallback != null,
showAlignmentButtons,
showHeaderStyle,
showListNumbers || showListBullets || showListCheck || showCodeBlock,
showQuote || showIndent,
@ -220,21 +224,37 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
(isButtonGroupShown[1] ||
isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4]))
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showHeaderStyle)
SelectHeaderStyleButton(
if (showAlignmentButtons)
SelectAlignmentButton(
controller: controller,
iconSize: toolbarIconSize,
),
if (isButtonGroupShown[1] &&
(isButtonGroupShown[2] ||
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(
indent: 12,
endIndent: 12,
@ -268,8 +288,8 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
icon: Icons.code,
iconSize: toolbarIconSize,
),
if (isButtonGroupShown[2] &&
(isButtonGroupShown[3] || isButtonGroupShown[4]))
if (isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5]))
VerticalDivider(
indent: 12,
endIndent: 12,
@ -296,7 +316,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
controller: controller,
isIncrease: false,
),
if (isButtonGroupShown[3] && isButtonGroupShown[4])
if (isButtonGroupShown[4] && isButtonGroupShown[5])
VerticalDivider(
indent: 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
Widget build(BuildContext context) {
final isInCodeBlock =
_selectionStyle.attributes.containsKey(Attribute.codeBlock.key);
final isEnabled =
!isInCodeBlock || Attribute.list.key == Attribute.codeBlock.key;
return widget.childBuilder(
context,
Attribute.unchecked,
widget.icon,
widget.fillColor,
_isToggled,
isEnabled ? _toggleAttribute : null,
_toggleAttribute,
widget.iconSize,
);
}

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

Loading…
Cancel
Save