mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: add checkbox and heading style
This commit is contained in:
parent
612e3dd50f
commit
734b642fcc
@ -68,7 +68,7 @@
|
|||||||
{ "insert": " your ", "attributes": { "bold": true } },
|
{ "insert": " your ", "attributes": { "bold": true } },
|
||||||
{ "insert": "writing", "attributes": { "underline": true } },
|
{ "insert": "writing", "attributes": { "underline": true } },
|
||||||
{
|
{
|
||||||
"insert": " howeverv you like.",
|
"insert": " however you like.",
|
||||||
"attributes": { "strikethrough": true }
|
"attributes": { "strikethrough": true }
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
|
"subtype": "heading",
|
||||||
"heading": "h1"
|
"heading": "h1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -28,9 +29,68 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
|
"subtype": "heading",
|
||||||
"heading": "h2"
|
"heading": "h2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "Here are the plugin demos:"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"subtype": "heading",
|
||||||
|
"heading": "h3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "Checkbox example ......"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"subtype": "checkbox",
|
||||||
|
"checkbox": false
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "AAA Checkbox example ......\nAAA Checkbox example ......"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"subtype": "checkbox",
|
||||||
|
"checkbox": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "BBB Checkbox example ......"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"subtype": "checkbox",
|
||||||
|
"checkbox": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "Raw text example ......"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": [
|
"delta": [
|
||||||
@ -39,6 +99,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
|
"subtype": "heading",
|
||||||
"heading": "h3"
|
"heading": "h3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -97,6 +158,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
|
"subtype": "heading",
|
||||||
"heading": "h3"
|
"heading": "h3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:flowy_editor/render/rich_text/checkbox_text.dart';
|
||||||
import 'package:flowy_editor/render/rich_text/flowy_rich_text.dart';
|
import 'package:flowy_editor/render/rich_text/flowy_rich_text.dart';
|
||||||
|
import 'package:flowy_editor/render/rich_text/heading_text.dart';
|
||||||
import 'package:flowy_editor/service/service.dart';
|
import 'package:flowy_editor/service/service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@ -43,6 +45,8 @@ class EditorState {
|
|||||||
}) {
|
}) {
|
||||||
// FIXME: abstract render plugins as a service.
|
// FIXME: abstract render plugins as a service.
|
||||||
renderPlugins.register('text', RichTextNodeWidgetBuilder.create);
|
renderPlugins.register('text', RichTextNodeWidgetBuilder.create);
|
||||||
|
renderPlugins.register('text/checkbox', CheckboxNodeWidgetBuilder.create);
|
||||||
|
renderPlugins.register('text/heading', HeadingTextNodeWidgetBuilder.create);
|
||||||
undoManager.state = this;
|
undoManager.state = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,132 @@
|
|||||||
|
import 'package:flowy_editor/document/node.dart';
|
||||||
|
import 'package:flowy_editor/editor_state.dart';
|
||||||
|
import 'package:flowy_editor/infra/flowy_svg.dart';
|
||||||
|
import 'package:flowy_editor/operation/transaction_builder.dart';
|
||||||
|
import 'package:flowy_editor/render/node_widget_builder.dart';
|
||||||
|
import 'package:flowy_editor/render/render_plugins.dart';
|
||||||
|
import 'package:flowy_editor/render/rich_text/default_selectable.dart';
|
||||||
|
import 'package:flowy_editor/render/rich_text/flowy_rich_text.dart';
|
||||||
|
import 'package:flowy_editor/render/rich_text/rich_text_style.dart';
|
||||||
|
import 'package:flowy_editor/render/selection/selectable.dart';
|
||||||
|
import 'package:flowy_editor/extensions/object_extensions.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CheckboxNodeWidgetBuilder extends NodeWidgetBuilder {
|
||||||
|
CheckboxNodeWidgetBuilder.create({
|
||||||
|
required super.editorState,
|
||||||
|
required super.node,
|
||||||
|
required super.key,
|
||||||
|
}) : super.create();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CheckboxNodeWidget(
|
||||||
|
key: key,
|
||||||
|
textNode: node as TextNode,
|
||||||
|
editorState: editorState,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CheckboxNodeWidget extends StatefulWidget {
|
||||||
|
const CheckboxNodeWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.textNode,
|
||||||
|
required this.editorState,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final TextNode textNode;
|
||||||
|
final EditorState editorState;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CheckboxNodeWidget> createState() => _CheckboxNodeWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
||||||
|
with Selectable, DefaultSelectable {
|
||||||
|
final _checkboxKey = GlobalKey(debugLabel: 'checkbox');
|
||||||
|
final _richTextKey = GlobalKey(debugLabel: 'checkbox_text');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Selectable<StatefulWidget> get forward =>
|
||||||
|
_richTextKey.currentState as Selectable;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Offset get baseOffset {
|
||||||
|
final width = _checkboxKey.currentContext
|
||||||
|
?.findRenderObject()
|
||||||
|
?.unwrapOrNull<RenderBox>()
|
||||||
|
?.size
|
||||||
|
.width;
|
||||||
|
if (width != null) {
|
||||||
|
return Offset(width, 0);
|
||||||
|
}
|
||||||
|
return Offset.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.textNode.children.isEmpty) {
|
||||||
|
return _buildWithSingle(context);
|
||||||
|
} else {
|
||||||
|
return _buildWithChildren(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWithSingle(BuildContext context) {
|
||||||
|
final check = widget.textNode.attributes.checkbox;
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
child: FlowySvg(
|
||||||
|
key: _checkboxKey,
|
||||||
|
name: check ? 'check' : 'uncheck',
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
debugPrint('[Checkbox] onTap...');
|
||||||
|
TransactionBuilder(widget.editorState)
|
||||||
|
..updateNode(widget.textNode, {
|
||||||
|
'checkbox': !check,
|
||||||
|
})
|
||||||
|
..commit();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FlowyRichText(
|
||||||
|
key: _richTextKey,
|
||||||
|
textNode: widget.textNode,
|
||||||
|
editorState: widget.editorState,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWithChildren(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildWithSingle(context),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: widget.textNode.children
|
||||||
|
.map(
|
||||||
|
(child) => widget.editorState.renderPlugins.buildWidget(
|
||||||
|
context: NodeWidgetContext(
|
||||||
|
buildContext: context,
|
||||||
|
node: child,
|
||||||
|
editorState: widget.editorState,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
import 'package:flowy_editor/document/position.dart';
|
||||||
|
import 'package:flowy_editor/document/selection.dart';
|
||||||
|
import 'package:flowy_editor/render/selection/selectable.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
mixin DefaultSelectable {
|
||||||
|
Selectable get forward;
|
||||||
|
|
||||||
|
Offset get baseOffset;
|
||||||
|
|
||||||
|
Position getPositionInOffset(Offset start) =>
|
||||||
|
forward.getPositionInOffset(start);
|
||||||
|
|
||||||
|
Rect getCursorRectInPosition(Position position) =>
|
||||||
|
forward.getCursorRectInPosition(position).shift(baseOffset);
|
||||||
|
|
||||||
|
List<Rect> getRectsInSelection(Selection selection) => forward
|
||||||
|
.getRectsInSelection(selection)
|
||||||
|
.map((rect) => rect.shift(baseOffset))
|
||||||
|
.toList(growable: false);
|
||||||
|
|
||||||
|
Selection getSelectionInRange(Offset start, Offset end) =>
|
||||||
|
forward.getSelectionInRange(start, end);
|
||||||
|
|
||||||
|
Position start() => forward.start();
|
||||||
|
|
||||||
|
Position end() => forward.end();
|
||||||
|
}
|
@ -32,11 +32,14 @@ class RichTextNodeWidgetBuilder extends NodeWidgetBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef FlowyTextSpanDecorator = TextSpan Function(TextSpan textSpan);
|
||||||
|
|
||||||
class FlowyRichText extends StatefulWidget {
|
class FlowyRichText extends StatefulWidget {
|
||||||
const FlowyRichText({
|
const FlowyRichText({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.cursorHeight,
|
this.cursorHeight,
|
||||||
this.cursorWidth = 2.0,
|
this.cursorWidth = 2.0,
|
||||||
|
this.textSpanDecorator,
|
||||||
required this.textNode,
|
required this.textNode,
|
||||||
required this.editorState,
|
required this.editorState,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -45,6 +48,7 @@ class FlowyRichText extends StatefulWidget {
|
|||||||
final double cursorWidth;
|
final double cursorWidth;
|
||||||
final TextNode textNode;
|
final TextNode textNode;
|
||||||
final EditorState editorState;
|
final EditorState editorState;
|
||||||
|
final FlowyTextSpanDecorator? textSpanDecorator;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FlowyRichText> createState() => _FlowyRichTextState();
|
State<FlowyRichText> createState() => _FlowyRichTextState();
|
||||||
@ -70,7 +74,7 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
|||||||
} else if (attributes.quote == true) {
|
} else if (attributes.quote == true) {
|
||||||
return _buildQuotedRichText(context);
|
return _buildQuotedRichText(context);
|
||||||
} else if (attributes.heading != null) {
|
} else if (attributes.heading != null) {
|
||||||
return _buildHeadingRichText(context);
|
// return _buildHeadingRichText(context);
|
||||||
} else if (attributes.number != null) {
|
} else if (attributes.number != null) {
|
||||||
return _buildNumberListRichText(context);
|
return _buildNumberListRichText(context);
|
||||||
}
|
}
|
||||||
@ -87,14 +91,13 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
|||||||
@override
|
@override
|
||||||
Rect getCursorRectInPosition(Position position) {
|
Rect getCursorRectInPosition(Position position) {
|
||||||
final textPosition = TextPosition(offset: position.offset);
|
final textPosition = TextPosition(offset: position.offset);
|
||||||
final baseRect = frontWidgetRect();
|
|
||||||
final cursorOffset =
|
final cursorOffset =
|
||||||
_renderParagraph.getOffsetForCaret(textPosition, Rect.zero);
|
_renderParagraph.getOffsetForCaret(textPosition, Rect.zero);
|
||||||
final cursorHeight = widget.cursorHeight ??
|
final cursorHeight = widget.cursorHeight ??
|
||||||
_renderParagraph.getFullHeightForCaret(textPosition) ??
|
_renderParagraph.getFullHeightForCaret(textPosition) ??
|
||||||
5.0; // default height
|
5.0; // default height
|
||||||
return Rect.fromLTWH(
|
return Rect.fromLTWH(
|
||||||
baseRect.centerRight.dx + cursorOffset.dx - (widget.cursorWidth / 2),
|
cursorOffset.dx - (widget.cursorWidth / 2),
|
||||||
cursorOffset.dy,
|
cursorOffset.dy,
|
||||||
widget.cursorWidth,
|
widget.cursorWidth,
|
||||||
cursorHeight,
|
cursorHeight,
|
||||||
@ -138,11 +141,7 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRichText(BuildContext context) {
|
Widget _buildRichText(BuildContext context) {
|
||||||
if (_textNode.children.isEmpty) {
|
return _buildSingleRichText(context);
|
||||||
return _buildSingleRichText(context);
|
|
||||||
} else {
|
|
||||||
return _buildRichTextWithChildren(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRichTextWithChildren(BuildContext context) {
|
Widget _buildRichTextWithChildren(BuildContext context) {
|
||||||
@ -166,10 +165,11 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSingleRichText(BuildContext context) {
|
Widget _buildSingleRichText(BuildContext context) {
|
||||||
return SizedBox(
|
return RichText(
|
||||||
width:
|
key: _textKey,
|
||||||
MediaQuery.of(context).size.width - 20, // FIXME: use the const value
|
text: widget.textSpanDecorator != null
|
||||||
child: RichText(key: _textKey, text: _decorateTextSpanWithGlobalStyle),
|
? widget.textSpanDecorator!(_decorateTextSpanWithGlobalStyle)
|
||||||
|
: _decorateTextSpanWithGlobalStyle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
import 'package:flowy_editor/document/node.dart';
|
||||||
|
import 'package:flowy_editor/editor_state.dart';
|
||||||
|
import 'package:flowy_editor/infra/flowy_svg.dart';
|
||||||
|
import 'package:flowy_editor/render/node_widget_builder.dart';
|
||||||
|
import 'package:flowy_editor/render/render_plugins.dart';
|
||||||
|
import 'package:flowy_editor/render/rich_text/default_selectable.dart';
|
||||||
|
import 'package:flowy_editor/render/rich_text/flowy_rich_text.dart';
|
||||||
|
import 'package:flowy_editor/render/rich_text/rich_text_style.dart';
|
||||||
|
import 'package:flowy_editor/render/selection/selectable.dart';
|
||||||
|
import 'package:flowy_editor/extensions/object_extensions.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class HeadingTextNodeWidgetBuilder extends NodeWidgetBuilder {
|
||||||
|
HeadingTextNodeWidgetBuilder.create({
|
||||||
|
required super.editorState,
|
||||||
|
required super.node,
|
||||||
|
required super.key,
|
||||||
|
}) : super.create();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return HeadingTextNodeWidget(
|
||||||
|
key: key,
|
||||||
|
textNode: node as TextNode,
|
||||||
|
editorState: editorState,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HeadingTextNodeWidget extends StatefulWidget {
|
||||||
|
const HeadingTextNodeWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.textNode,
|
||||||
|
required this.editorState,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final TextNode textNode;
|
||||||
|
final EditorState editorState;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HeadingTextNodeWidget> createState() => _HeadingTextNodeWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// customize
|
||||||
|
|
||||||
|
class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
|
||||||
|
with Selectable, DefaultSelectable {
|
||||||
|
final _richTextKey = GlobalKey(debugLabel: 'heading_text');
|
||||||
|
final topPadding = 5.0;
|
||||||
|
final bottomPadding = 2.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Selectable<StatefulWidget> get forward =>
|
||||||
|
_richTextKey.currentState as Selectable;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Offset get baseOffset {
|
||||||
|
return Offset(0, topPadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: topPadding,
|
||||||
|
),
|
||||||
|
FlowyRichText(
|
||||||
|
key: _richTextKey,
|
||||||
|
textSpanDecorator: _textSpanDecorator,
|
||||||
|
textNode: widget.textNode,
|
||||||
|
editorState: widget.editorState,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: bottomPadding,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextSpan _textSpanDecorator(TextSpan textSpan) {
|
||||||
|
return TextSpan(
|
||||||
|
children: textSpan.children
|
||||||
|
?.whereType<TextSpan>()
|
||||||
|
.map(
|
||||||
|
(span) => TextSpan(
|
||||||
|
text: span.text,
|
||||||
|
style: span.style?.copyWith(
|
||||||
|
fontSize: widget.textNode.attributes.fontSize,
|
||||||
|
),
|
||||||
|
recognizer: span.recognizer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -25,12 +25,15 @@ class StyleKey {
|
|||||||
static String font = 'font';
|
static String font = 'font';
|
||||||
static String href = 'href';
|
static String href = 'href';
|
||||||
|
|
||||||
static String heading = 'heading';
|
|
||||||
static String quote = 'quote';
|
static String quote = 'quote';
|
||||||
static String list = 'list';
|
static String list = 'list';
|
||||||
static String number = 'number';
|
static String number = 'number';
|
||||||
static String todo = 'todo';
|
static String todo = 'todo';
|
||||||
static String code = 'code';
|
static String code = 'code';
|
||||||
|
|
||||||
|
static String subtype = 'subtype';
|
||||||
|
static String checkbox = 'checkbox';
|
||||||
|
static String heading = 'heading';
|
||||||
}
|
}
|
||||||
|
|
||||||
double baseFontSize = 16.0;
|
double baseFontSize = 16.0;
|
||||||
@ -100,6 +103,13 @@ extension NodeAttributesExtensions on Attributes {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get checkbox {
|
||||||
|
if (containsKey(StyleKey.checkbox) && this[StyleKey.checkbox] is bool) {
|
||||||
|
return this[StyleKey.checkbox];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DeltaAttributesExtensions on Attributes {
|
extension DeltaAttributesExtensions on Attributes {
|
||||||
|
@ -23,7 +23,7 @@ class FlowyEditor extends StatefulWidget {
|
|||||||
final EditorState editorState;
|
final EditorState editorState;
|
||||||
final List<FlowyKeyEventHandler> keyEventHandlers;
|
final List<FlowyKeyEventHandler> keyEventHandlers;
|
||||||
|
|
||||||
/// Shortcusts
|
/// shortcuts
|
||||||
final FloatingShortcuts shortcuts;
|
final FloatingShortcuts shortcuts;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -165,7 +165,7 @@ class _FlowySelectionState extends State<FlowySelection>
|
|||||||
(recognizer) {
|
(recognizer) {
|
||||||
recognizer.onTapDown = _onTapDown;
|
recognizer.onTapDown = _onTapDown;
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user