saminsohag

ui improved

@@ -110,6 +110,9 @@ $hello$ @@ -110,6 +110,9 @@ $hello$
110 shape: const RoundedRectangleBorder( 110 shape: const RoundedRectangleBorder(
111 side: BorderSide(width: 1), 111 side: BorderSide(width: 1),
112 ), 112 ),
  113 + child: LayoutBuilder(builder: (context, constraints) {
  114 + return SingleChildScrollView(
  115 + scrollDirection: Axis.horizontal,
113 child: TexMarkdown( 116 child: TexMarkdown(
114 _controller.text, 117 _controller.text,
115 onLinkTab: (url, title) { 118 onLinkTab: (url, title) {
@@ -117,11 +120,13 @@ $hello$ @@ -117,11 +120,13 @@ $hello$
117 log(url, name: "url"); 120 log(url, name: "url");
118 }, 121 },
119 style: const TextStyle( 122 style: const TextStyle(
120 - color: Colors.green, 123 + // color: Colors.green,
121 ), 124 ),
122 ), 125 ),
123 ); 126 );
124 }), 127 }),
  128 + );
  129 + }),
125 ], 130 ],
126 ), 131 ),
127 ), 132 ),
  1 +import 'package:flutter/material.dart';
  2 +
  3 +class CustomDivider extends LeafRenderObjectWidget {
  4 + const CustomDivider({super.key, this.height, this.color});
  5 + final Color? color;
  6 + final double? height;
  7 +
  8 + @override
  9 + RenderObject createRenderObject(BuildContext context) {
  10 + return RenderDivider(
  11 + color ?? Theme.of(context).colorScheme.onSurfaceVariant,
  12 + MediaQuery.of(context).size.width,
  13 + height ?? 2);
  14 + }
  15 +
  16 + @override
  17 + void updateRenderObject(
  18 + BuildContext context, covariant RenderDivider renderObject) {
  19 + renderObject.color =
  20 + color ?? Theme.of(context).colorScheme.onSurfaceVariant;
  21 + renderObject.height = height ?? 2;
  22 + renderObject.width = MediaQuery.of(context).size.width;
  23 + }
  24 +}
  25 +
  26 +class RenderDivider extends RenderBox {
  27 + RenderDivider(Color color, double width, double height)
  28 + : _color = color,
  29 + _height = height,
  30 + _width = width;
  31 + Color _color;
  32 + double _height;
  33 + double _width;
  34 + set color(Color value) {
  35 + if (value == _color) {
  36 + return;
  37 + }
  38 + _color = value;
  39 + markNeedsPaint();
  40 + }
  41 +
  42 + set height(double value) {
  43 + if (value == _height) {
  44 + return;
  45 + }
  46 + _height = value;
  47 + markNeedsLayout();
  48 + }
  49 +
  50 + set width(double value) {
  51 + if (value == _width) {
  52 + return;
  53 + }
  54 + _width = value;
  55 + markNeedsLayout();
  56 + }
  57 +
  58 + @override
  59 + Size computeDryLayout(BoxConstraints constraints) {
  60 + return BoxConstraints.tightFor(width: null, height: _height)
  61 + .enforce(constraints)
  62 + .smallest;
  63 + }
  64 +
  65 + @override
  66 + void performLayout() {
  67 + size = getDryLayout(constraints);
  68 + }
  69 +
  70 + @override
  71 + void paint(PaintingContext context, Offset offset) {
  72 + context.canvas.drawRect(offset & Size(Rect.largest.size.width, _height),
  73 + Paint()..color = _color);
  74 + }
  75 +}
  1 +import 'dart:math';
  2 +
  3 +import 'package:flutter/material.dart';
  4 +import 'package:flutter/rendering.dart';
  5 +
  6 +enum CustomRbSlot {
  7 + rb,
  8 + child,
  9 +}
  10 +
  11 +class CustomRb extends RenderObjectWidget
  12 + with SlottedMultiChildRenderObjectWidgetMixin<CustomRbSlot> {
  13 + const CustomRb(
  14 + {super.key, this.spacing = 5, required this.child, required this.value});
  15 + final Widget child;
  16 + final bool value;
  17 + final double spacing;
  18 +
  19 + @override
  20 + Widget? childForSlot(CustomRbSlot slot) {
  21 + switch (slot) {
  22 + case CustomRbSlot.rb:
  23 + return Radio(value: value, groupValue: true, onChanged: (value) {});
  24 + case CustomRbSlot.child:
  25 + return child;
  26 + }
  27 + }
  28 +
  29 + @override
  30 + SlottedContainerRenderObjectMixin<CustomRbSlot> createRenderObject(
  31 + BuildContext context) {
  32 + return RenderCustomRb(spacing);
  33 + }
  34 +
  35 + @override
  36 + void updateRenderObject(
  37 + BuildContext context, covariant RenderCustomRb renderObject) {
  38 + renderObject.spacing = spacing;
  39 + }
  40 +
  41 + @override
  42 + Iterable<CustomRbSlot> get slots => CustomRbSlot.values;
  43 +}
  44 +
  45 +class RenderCustomRb extends RenderBox
  46 + with SlottedContainerRenderObjectMixin<CustomRbSlot> {
  47 + RenderCustomRb(this._spacing);
  48 + double _spacing;
  49 + set spacing(double value) {
  50 + if (_spacing == value) {
  51 + return;
  52 + }
  53 + _spacing = value;
  54 + markNeedsLayout();
  55 + }
  56 +
  57 + RenderBox? get rb => childForSlot(CustomRbSlot.rb);
  58 + RenderBox? get body => childForSlot(CustomRbSlot.child);
  59 +
  60 + Size _layoutBox(RenderBox box, BoxConstraints constraints) {
  61 + box.layout(constraints, parentUsesSize: true);
  62 + return box.size;
  63 + }
  64 +
  65 + @override
  66 + void performLayout() {
  67 + if (rb == null || body == null) {
  68 + size = constraints.constrain(const Size(50, 10));
  69 + return;
  70 + }
  71 + rb;
  72 + Size rbSize = _layoutBox(rb!, const BoxConstraints(maxWidth: 50));
  73 + Size bodySize = _layoutBox(
  74 + body!,
  75 + BoxConstraints(
  76 + maxWidth: constraints.maxWidth - rbSize.width - _spacing));
  77 + body!.parentData = BoxParentData()
  78 + ..offset = Offset(rbSize.width + _spacing, 0);
  79 + rb!.parentData = BoxParentData()
  80 + ..offset = Offset(
  81 + 0,
  82 + body!.computeDistanceToActualBaseline(TextBaseline.alphabetic)! -
  83 + rb!.size.height / 1.5);
  84 + size = constraints.constrain(Size(bodySize.width + rbSize.width + _spacing,
  85 + max(rbSize.height, bodySize.height)));
  86 + }
  87 +
  88 + @override
  89 + void paint(PaintingContext context, Offset offset) {
  90 + context.paintChild(
  91 + body!, offset + (body!.parentData as BoxParentData).offset);
  92 + context.paintChild(rb!, offset + (rb!.parentData as BoxParentData).offset);
  93 + }
  94 +
  95 + @override
  96 + bool hitTestSelf(Offset position) {
  97 + return true;
  98 + }
  99 +
  100 + @override
  101 + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
  102 + for (final RenderBox child in children) {
  103 + final BoxParentData parentData = child.parentData! as BoxParentData;
  104 + final bool isHit = result.addWithPaintOffset(
  105 + offset: parentData.offset,
  106 + position: position,
  107 + hitTest: (BoxHitTestResult result, Offset transformed) {
  108 + assert(transformed == position - parentData.offset);
  109 + return child.hitTest(result, position: transformed);
  110 + },
  111 + );
  112 + if (isHit) {
  113 + return true;
  114 + }
  115 + }
  116 + return false;
  117 + }
  118 +}
  119 +
  120 +enum CustomCbSlot {
  121 + cb,
  122 + child,
  123 +}
  124 +
  125 +class CustomCb extends RenderObjectWidget
  126 + with SlottedMultiChildRenderObjectWidgetMixin<CustomCbSlot> {
  127 + const CustomCb(
  128 + {super.key, this.spacing = 5, required this.child, required this.value});
  129 + final Widget child;
  130 + final bool value;
  131 + final double spacing;
  132 +
  133 + @override
  134 + Widget? childForSlot(CustomCbSlot slot) {
  135 + switch (slot) {
  136 + case CustomCbSlot.cb:
  137 + return Checkbox(value: value, onChanged: (value) {});
  138 + case CustomCbSlot.child:
  139 + return child;
  140 + }
  141 + }
  142 +
  143 + @override
  144 + SlottedContainerRenderObjectMixin<CustomCbSlot> createRenderObject(
  145 + BuildContext context) {
  146 + return RenderCustomCb(spacing);
  147 + }
  148 +
  149 + @override
  150 + void updateRenderObject(
  151 + BuildContext context, covariant RenderCustomCb renderObject) {
  152 + renderObject.spacing = spacing;
  153 + }
  154 +
  155 + @override
  156 + Iterable<CustomCbSlot> get slots => CustomCbSlot.values;
  157 +}
  158 +
  159 +class RenderCustomCb extends RenderBox
  160 + with SlottedContainerRenderObjectMixin<CustomCbSlot> {
  161 + RenderCustomCb(this._spacing);
  162 + double _spacing;
  163 + set spacing(double value) {
  164 + if (_spacing == value) {
  165 + return;
  166 + }
  167 + _spacing = value;
  168 + markNeedsLayout();
  169 + }
  170 +
  171 + RenderBox? get rb => childForSlot(CustomCbSlot.cb);
  172 + RenderBox? get body => childForSlot(CustomCbSlot.child);
  173 +
  174 + Size _layoutBox(RenderBox box, BoxConstraints constraints) {
  175 + box.layout(constraints, parentUsesSize: true);
  176 + return box.size;
  177 + }
  178 +
  179 + @override
  180 + void performLayout() {
  181 + if (rb == null || body == null) {
  182 + size = constraints.constrain(const Size(50, 10));
  183 + return;
  184 + }
  185 + rb;
  186 + Size rbSize = _layoutBox(rb!, const BoxConstraints(maxWidth: 50));
  187 + Size bodySize = _layoutBox(
  188 + body!,
  189 + BoxConstraints(
  190 + maxWidth: constraints.maxWidth - rbSize.width - _spacing));
  191 + body!.parentData = BoxParentData()
  192 + ..offset = Offset(rbSize.width + _spacing, 0);
  193 + rb!.parentData = BoxParentData()
  194 + ..offset = Offset(
  195 + 0,
  196 + body!.computeDistanceToActualBaseline(TextBaseline.alphabetic)! -
  197 + rb!.size.height / 1.5);
  198 + size = constraints.constrain(Size(bodySize.width + rbSize.width + _spacing,
  199 + max(rbSize.height, bodySize.height)));
  200 + }
  201 +
  202 + @override
  203 + void paint(PaintingContext context, Offset offset) {
  204 + context.paintChild(
  205 + body!, offset + (body!.parentData as BoxParentData).offset);
  206 + context.paintChild(rb!, offset + (rb!.parentData as BoxParentData).offset);
  207 + }
  208 +
  209 + @override
  210 + bool hitTestSelf(Offset position) {
  211 + return true;
  212 + }
  213 +
  214 + @override
  215 + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
  216 + for (final RenderBox child in children) {
  217 + final BoxParentData parentData = child.parentData! as BoxParentData;
  218 + final bool isHit = result.addWithPaintOffset(
  219 + offset: parentData.offset,
  220 + position: position,
  221 + hitTest: (BoxHitTestResult result, Offset transformed) {
  222 + assert(transformed == position - parentData.offset);
  223 + return child.hitTest(result, position: transformed);
  224 + },
  225 + );
  226 + if (isHit) {
  227 + return true;
  228 + }
  229 + }
  230 + return false;
  231 + }
  232 +}
  1 +import 'package:flutter/material.dart';
  2 +import 'package:flutter/rendering.dart';
  3 +
  4 +class UnorderedListView extends SingleChildRenderObjectWidget {
  5 + const UnorderedListView(
  6 + {super.key,
  7 + this.spacing = 6,
  8 + this.padding = 10,
  9 + this.bulletColor,
  10 + this.bulletSize = 4,
  11 + required super.child});
  12 + final double bulletSize;
  13 + final double spacing;
  14 + final double padding;
  15 + final Color? bulletColor;
  16 +
  17 + @override
  18 + RenderObject createRenderObject(BuildContext context) {
  19 + return UnorderedListRenderObject(
  20 + spacing,
  21 + padding,
  22 + bulletColor ?? Theme.of(context).colorScheme.onSurface,
  23 + bulletSize,
  24 + );
  25 + }
  26 +
  27 + @override
  28 + void updateRenderObject(
  29 + BuildContext context, covariant UnorderedListRenderObject renderObject) {
  30 + renderObject.bulletColor =
  31 + bulletColor ?? Theme.of(context).colorScheme.onSurface;
  32 + renderObject.bulletSize = bulletSize;
  33 + renderObject.spacing = spacing;
  34 + renderObject.padding = padding;
  35 + }
  36 +}
  37 +
  38 +class UnorderedListRenderObject extends RenderProxyBox {
  39 + UnorderedListRenderObject(
  40 + double spacing,
  41 + double padding,
  42 + Color bulletColor,
  43 + this._bulletSize, {
  44 + RenderBox? child,
  45 + }) : _bulletColor = bulletColor,
  46 + _spacing = spacing,
  47 + _padding = padding,
  48 + super(child);
  49 + double _spacing;
  50 + double _padding;
  51 + Offset _bulletOffset = Offset.zero;
  52 + set spacing(double value) {
  53 + if (_spacing == value) {
  54 + return;
  55 + }
  56 + _spacing = value;
  57 + markNeedsLayout();
  58 + }
  59 +
  60 + set padding(double value) {
  61 + if (_padding == value) {
  62 + return;
  63 + }
  64 + _padding = value;
  65 + markNeedsLayout();
  66 + }
  67 +
  68 + Color _bulletColor;
  69 + double _bulletSize;
  70 + set bulletSize(double value) {
  71 + if (_bulletSize == value) {
  72 + return;
  73 + }
  74 + _bulletSize = value;
  75 + markNeedsLayout();
  76 + }
  77 +
  78 + set bulletColor(Color value) {
  79 + if (_bulletColor == value) {
  80 + return;
  81 + }
  82 + _bulletColor = value;
  83 + markNeedsLayout();
  84 + }
  85 +
  86 + @override
  87 + double computeMinIntrinsicWidth(double height) {
  88 + child!.layout(
  89 + BoxConstraints(
  90 + maxWidth:
  91 + constraints.maxWidth - _spacing - 6 - _bulletSize - _padding,
  92 + ),
  93 + parentUsesSize: true);
  94 + return child!.size.width;
  95 + }
  96 +
  97 + @override
  98 + double computeMaxIntrinsicWidth(double height) {
  99 + child!.layout(
  100 + BoxConstraints(
  101 + maxWidth:
  102 + constraints.maxWidth - _spacing - 6 - _bulletSize - _padding,
  103 + ),
  104 + parentUsesSize: true);
  105 + return child!.size.width;
  106 + }
  107 +
  108 + @override
  109 + double computeMinIntrinsicHeight(double width) {
  110 + child!.layout(
  111 + BoxConstraints(
  112 + maxWidth:
  113 + constraints.maxWidth - _spacing - 6 - _bulletSize - _padding,
  114 + ),
  115 + parentUsesSize: true);
  116 + return child!.size.height;
  117 + }
  118 +
  119 + @override
  120 + double computeMaxIntrinsicHeight(double width) {
  121 + child!.layout(
  122 + BoxConstraints(
  123 + maxWidth:
  124 + constraints.maxWidth - _spacing - 6 - _bulletSize - _padding,
  125 + ),
  126 + parentUsesSize: true);
  127 + return child!.size.height;
  128 + }
  129 +
  130 + @override
  131 + void performLayout() {
  132 + super.performLayout();
  133 + if (child == null) {
  134 + return;
  135 + }
  136 + child!.layout(
  137 + BoxConstraints(
  138 + maxWidth:
  139 + constraints.maxWidth - _spacing - 6 - _bulletSize - _padding,
  140 + ),
  141 + parentUsesSize: true);
  142 + child!.parentData = BoxParentData()
  143 + ..offset = Offset(_spacing + _padding + 6 + _bulletSize, 0);
  144 + var value = child!.computeDistanceToActualBaseline(TextBaseline.alphabetic);
  145 + _bulletOffset = Offset(4 + _padding, value! - _bulletSize);
  146 + size = constraints.constrain(Size(
  147 + child!.size.width + _spacing + _padding + 6 + _bulletSize,
  148 + child!.size.height));
  149 + }
  150 +
  151 + @override
  152 + void paint(PaintingContext context, Offset offset) {
  153 + if (child == null) {
  154 + return;
  155 + }
  156 + context.paintChild(
  157 + child!, offset + (child!.parentData as BoxParentData).offset);
  158 + context.canvas.drawCircle(
  159 + offset + _bulletOffset, _bulletSize, Paint()..color = _bulletColor);
  160 + }
  161 +}
  162 +
  163 +class OrderedListView extends SingleChildRenderObjectWidget {
  164 + final String no;
  165 + final double spacing;
  166 + final double padding;
  167 + const OrderedListView(
  168 + {super.key,
  169 + this.spacing = 6,
  170 + this.padding = 10,
  171 + TextStyle? style,
  172 + required super.child,
  173 + required this.no})
  174 + : _style = style;
  175 + final TextStyle? _style;
  176 +
  177 + TextStyle getStyle(BuildContext context) {
  178 + if (_style == null || _style!.inherit) {
  179 + return DefaultTextStyle.of(context).style.merge(_style);
  180 + }
  181 + return _style!;
  182 + }
  183 +
  184 + @override
  185 + RenderObject createRenderObject(BuildContext context) {
  186 + return OrderedListRenderObject(
  187 + no,
  188 + spacing,
  189 + padding,
  190 + getStyle(context),
  191 + );
  192 + }
  193 +
  194 + @override
  195 + void updateRenderObject(
  196 + BuildContext context, covariant OrderedListRenderObject renderObject) {
  197 + renderObject.no = no;
  198 + renderObject.spacing = spacing;
  199 + renderObject.padding = padding;
  200 + renderObject.style = getStyle(context);
  201 + }
  202 +}
  203 +
  204 +class OrderedListRenderObject extends RenderProxyBox {
  205 + OrderedListRenderObject(
  206 + String no,
  207 + double spacing,
  208 + double padding,
  209 + TextStyle style, {
  210 + RenderBox? child,
  211 + }) : _no = no,
  212 + _style = style,
  213 + _spacing = spacing,
  214 + _padding = padding,
  215 + super(child);
  216 + double _spacing;
  217 + double _padding;
  218 + Offset _ptOffset = Offset.zero;
  219 + set spacing(double value) {
  220 + if (_spacing == value) {
  221 + return;
  222 + }
  223 + _spacing = value;
  224 + markNeedsLayout();
  225 + }
  226 +
  227 + set padding(double value) {
  228 + if (_padding == value) {
  229 + return;
  230 + }
  231 + _padding = value;
  232 + markNeedsLayout();
  233 + }
  234 +
  235 + TextStyle _style;
  236 + set style(TextStyle value) {
  237 + _style = value;
  238 + markNeedsLayout();
  239 + }
  240 +
  241 + String _no;
  242 + set no(String value) {
  243 + if (_no == value) {
  244 + return;
  245 + }
  246 + _no = value;
  247 + markNeedsLayout();
  248 + }
  249 +
  250 + @override
  251 + double computeMinIntrinsicHeight(double width) {
  252 + pt = TextPainter(
  253 + text: TextSpan(
  254 + text: _no,
  255 + style: _style,
  256 + ),
  257 + textDirection: TextDirection.ltr);
  258 + pt.layout(maxWidth: constraints.maxWidth - 50 - _spacing - _padding);
  259 + return child!
  260 + .computeMinIntrinsicHeight(width - pt.width - _spacing - _padding);
  261 + }
  262 +
  263 + @override
  264 + double computeMaxIntrinsicHeight(double width) {
  265 + pt = TextPainter(
  266 + text: TextSpan(
  267 + text: _no,
  268 + style: _style,
  269 + ),
  270 + textDirection: TextDirection.ltr);
  271 + pt.layout(maxWidth: constraints.maxWidth - 50 - _spacing - _padding);
  272 + return child!
  273 + .computeMaxIntrinsicHeight(width - pt.width - _spacing - _padding);
  274 + }
  275 +
  276 + late TextPainter pt;
  277 + @override
  278 + void performLayout() {
  279 + super.performLayout();
  280 + if (child == null) {
  281 + return;
  282 + }
  283 + pt = TextPainter(
  284 + text: TextSpan(
  285 + text: _no,
  286 + style: _style,
  287 + ),
  288 + textDirection: TextDirection.ltr);
  289 + pt.layout(maxWidth: constraints.maxWidth - 50 - _spacing - _padding);
  290 + child!.layout(
  291 + BoxConstraints(
  292 + maxWidth: constraints.maxWidth - pt.width - _spacing - _padding,
  293 + ),
  294 + parentUsesSize: true);
  295 + child!.parentData = BoxParentData()
  296 + ..offset = Offset(_spacing + _padding + pt.width, 0);
  297 + var value = child!.computeDistanceToActualBaseline(TextBaseline.alphabetic);
  298 + _ptOffset = Offset(_padding,
  299 + value! - pt.computeDistanceToActualBaseline(TextBaseline.alphabetic));
  300 + size = constraints.constrain(Size(
  301 + child!.size.width + _spacing + _padding + pt.width,
  302 + child!.size.height));
  303 + }
  304 +
  305 + @override
  306 + void paint(PaintingContext context, Offset offset) {
  307 + if (child == null) {
  308 + return;
  309 + }
  310 + context.paintChild(
  311 + child!,
  312 + offset + (child!.parentData as BoxParentData).offset,
  313 + );
  314 + pt.paint(context.canvas, offset + _ptOffset);
  315 + }
  316 +}
1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
  2 +import 'package:tex_markdown/custom_widgets/custom_divider.dart';
  3 +import 'package:tex_markdown/custom_widgets/custom_rb_cb.dart';
  4 +import 'package:tex_markdown/custom_widgets/unordered_ordered_list.dart';
2 import 'package:tex_text/tex_text.dart'; 5 import 'package:tex_text/tex_text.dart';
3 import 'md_widget.dart'; 6 import 'md_widget.dart';
4 7
5 /// Markdown components 8 /// Markdown components
6 abstract class MarkdownComponent { 9 abstract class MarkdownComponent {
7 static final List<MarkdownComponent> components = [ 10 static final List<MarkdownComponent> components = [
  11 + // TableMd(),
8 HTag(), 12 HTag(),
9 BoldMd(), 13 BoldMd(),
10 ItalicMd(), 14 ItalicMd(),
11 - ATagMd(),  
12 ImageMd(), 15 ImageMd(),
  16 + ATagMd(),
13 UnOrderedList(), 17 UnOrderedList(),
14 OrderedList(), 18 OrderedList(),
15 RadioButtonMd(), 19 RadioButtonMd(),
@@ -38,7 +42,7 @@ abstract class MarkdownComponent { @@ -38,7 +42,7 @@ abstract class MarkdownComponent {
38 } 42 }
39 43
40 /// Generate widget for markdown widget 44 /// Generate widget for markdown widget
41 - static Widget generate( 45 + static List<InlineSpan> generate(
42 BuildContext context, 46 BuildContext context,
43 String text, 47 String text,
44 TextStyle? style, 48 TextStyle? style,
@@ -52,7 +56,7 @@ abstract class MarkdownComponent { @@ -52,7 +56,7 @@ abstract class MarkdownComponent {
52 if (each is InlineMd) { 56 if (each is InlineMd) {
53 spans.add(each.span( 57 spans.add(each.span(
54 context, 58 context,
55 - element, 59 + element.trim(),
56 style, 60 style,
57 onLinkTab, 61 onLinkTab,
58 )); 62 ));
@@ -65,7 +69,7 @@ abstract class MarkdownComponent { @@ -65,7 +69,7 @@ abstract class MarkdownComponent {
65 } else { 69 } else {
66 if (each is BlockMd) { 70 if (each is BlockMd) {
67 spans.add( 71 spans.add(
68 - each.span(context, element, style, onLinkTab), 72 + each.span(context, element.trim(), style, onLinkTab),
69 ); 73 );
70 } 74 }
71 } 75 }
@@ -74,12 +78,13 @@ abstract class MarkdownComponent { @@ -74,12 +78,13 @@ abstract class MarkdownComponent {
74 } 78 }
75 }, 79 },
76 ); 80 );
77 - return Text.rich(  
78 - TextSpan(  
79 - children: List.from(spans),  
80 - ),  
81 - textAlign: TextAlign.left,  
82 - ); 81 + // return Text.rich(
  82 + // TextSpan(
  83 + // children: List.from(spans),
  84 + // ),
  85 + // // textAlign: TextAlign.left,
  86 + // );
  87 + return spans;
83 } 88 }
84 89
85 InlineSpan span( 90 InlineSpan span(
@@ -119,11 +124,27 @@ abstract class BlockMd extends MarkdownComponent { @@ -119,11 +124,27 @@ abstract class BlockMd extends MarkdownComponent {
119 TextStyle? style, 124 TextStyle? style,
120 final void Function(String url, String title)? onLinkTab, 125 final void Function(String url, String title)? onLinkTab,
121 ) { 126 ) {
122 - return WidgetSpan(  
123 - child: Align(  
124 - alignment: Alignment.centerLeft, 127 + return TextSpan(
  128 + children: [
  129 + const TextSpan(
  130 + text: "\n",
  131 + style: TextStyle(
  132 + fontSize: 0,
  133 + ),
  134 + ),
  135 + WidgetSpan(
125 child: build(context, text, style, onLinkTab), 136 child: build(context, text, style, onLinkTab),
  137 + alignment: PlaceholderAlignment.middle,
126 ), 138 ),
  139 + const TextSpan(
  140 + text: "\n",
  141 + style: TextStyle(fontSize: 0),
  142 + ),
  143 + ],
  144 + // child: Align(
  145 + // alignment: Alignment.centerLeft,
  146 + // child: build(context, text, style, onLinkTab),
  147 + // ),
127 ); 148 );
128 } 149 }
129 150
@@ -148,11 +169,9 @@ class HTag extends BlockMd { @@ -148,11 +169,9 @@ class HTag extends BlockMd {
148 final void Function(String url, String title)? onLinkTab, 169 final void Function(String url, String title)? onLinkTab,
149 ) { 170 ) {
150 var match = exp.firstMatch(text.trim()); 171 var match = exp.firstMatch(text.trim());
151 - return Column( 172 + return Text.rich(TextSpan(
152 children: [ 173 children: [
153 - Row(  
154 - children: [  
155 - Expanded( 174 + WidgetSpan(
156 child: TexText("${match?[2]}", 175 child: TexText("${match?[2]}",
157 style: [ 176 style: [
158 Theme.of(context) 177 Theme.of(context)
@@ -181,17 +200,22 @@ class HTag extends BlockMd { @@ -181,17 +200,22 @@ class HTag extends BlockMd {
181 ?.copyWith(color: style?.color), 200 ?.copyWith(color: style?.color),
182 ][match![1]!.length - 1]), 201 ][match![1]!.length - 1]),
183 ), 202 ),
184 - ], 203 + if (match[1]!.length == 1) ...[
  204 + const TextSpan(
  205 + text: "\n",
  206 + style: TextStyle(fontSize: 0, height: 0),
  207 + ),
  208 + WidgetSpan(
  209 + alignment: PlaceholderAlignment.top,
  210 + child: CustomDivider(
  211 + height: 2,
  212 + color: style?.color ??
  213 + Theme.of(context).colorScheme.onSurfaceVariant,
185 ), 214 ),
186 - if (match[1]!.length == 1)  
187 - Divider(  
188 - height: 6,  
189 - thickness: 2,  
190 - color:  
191 - style?.color ?? Theme.of(context).colorScheme.onSurfaceVariant,  
192 ), 215 ),
193 ], 216 ],
194 - ); 217 + ],
  218 + ));
195 } 219 }
196 220
197 @override 221 @override
@@ -212,9 +236,8 @@ class HrLine extends BlockMd { @@ -212,9 +236,8 @@ class HrLine extends BlockMd {
212 TextStyle? style, 236 TextStyle? style,
213 final void Function(String url, String title)? onLinkTab, 237 final void Function(String url, String title)? onLinkTab,
214 ) { 238 ) {
215 - return Divider(  
216 - height: 6,  
217 - thickness: 2, 239 + return CustomDivider(
  240 + height: 2,
218 color: style?.color ?? Theme.of(context).colorScheme.onSurfaceVariant, 241 color: style?.color ?? Theme.of(context).colorScheme.onSurfaceVariant,
219 ); 242 );
220 } 243 }
@@ -237,27 +260,13 @@ class CheckBoxMd extends BlockMd { @@ -237,27 +260,13 @@ class CheckBoxMd extends BlockMd {
237 final void Function(String url, String title)? onLinkTab, 260 final void Function(String url, String title)? onLinkTab,
238 ) { 261 ) {
239 var match = exp.firstMatch(text.trim()); 262 var match = exp.firstMatch(text.trim());
240 - return Row(  
241 - crossAxisAlignment: CrossAxisAlignment.center,  
242 - mainAxisSize: MainAxisSize.min,  
243 - children: [  
244 - Padding(  
245 - padding: const EdgeInsets.symmetric(horizontal: 10),  
246 - child: Checkbox(  
247 - // value: true, 263 + return CustomCb(
248 value: ("${match?[1]}" == "x"), 264 value: ("${match?[1]}" == "x"),
249 - onChanged: (value) {},  
250 - fillColor: ButtonStyleButton.allOrNull(style?.color),  
251 - ),  
252 - ),  
253 - Expanded(  
254 child: MdWidget( 265 child: MdWidget(
255 "${match?[2]}", 266 "${match?[2]}",
256 onLinkTab: onLinkTab, 267 onLinkTab: onLinkTab,
257 style: style, 268 style: style,
258 ), 269 ),
259 - ),  
260 - ],  
261 ); 270 );
262 } 271 }
263 272
@@ -283,27 +292,13 @@ class RadioButtonMd extends BlockMd { @@ -283,27 +292,13 @@ class RadioButtonMd extends BlockMd {
283 final void Function(String url, String title)? onLinkTab, 292 final void Function(String url, String title)? onLinkTab,
284 ) { 293 ) {
285 var match = exp.firstMatch(text.trim()); 294 var match = exp.firstMatch(text.trim());
286 - return Row(  
287 - crossAxisAlignment: CrossAxisAlignment.center,  
288 - mainAxisSize: MainAxisSize.min,  
289 - children: [  
290 - Padding(  
291 - padding: const EdgeInsets.symmetric(horizontal: 10),  
292 - child: Radio(  
293 - value: true,  
294 - groupValue: ("${match?[1]}" == "x"),  
295 - onChanged: (value) {},  
296 - fillColor: ButtonStyleButton.allOrNull(style?.color),  
297 - ),  
298 - ),  
299 - Expanded( 295 + return CustomRb(
  296 + value: ("${match?[1]}" == "x"),
300 child: MdWidget( 297 child: MdWidget(
301 "${match?[2]}", 298 "${match?[2]}",
302 onLinkTab: onLinkTab, 299 onLinkTab: onLinkTab,
303 style: style, 300 style: style,
304 ), 301 ),
305 - ),  
306 - ],  
307 ); 302 );
308 } 303 }
309 304
@@ -329,26 +324,13 @@ class UnOrderedList extends BlockMd { @@ -329,26 +324,13 @@ class UnOrderedList extends BlockMd {
329 final void Function(String url, String title)? onLinkTab, 324 final void Function(String url, String title)? onLinkTab,
330 ) { 325 ) {
331 var match = exp.firstMatch(text.trim()); 326 var match = exp.firstMatch(text.trim());
332 - return Row(  
333 - crossAxisAlignment: CrossAxisAlignment.center,  
334 - mainAxisSize: MainAxisSize.max,  
335 - children: [  
336 - Padding(  
337 - padding: const EdgeInsets.symmetric(horizontal: 10),  
338 - child: Icon(  
339 - Icons.circle,  
340 - color: style?.color,  
341 - size: 8,  
342 - ),  
343 - ),  
344 - Expanded( 327 + return UnorderedListView(
  328 + bulletColor: style?.color,
345 child: MdWidget( 329 child: MdWidget(
346 "${match?[2]}", 330 "${match?[2]}",
347 onLinkTab: onLinkTab, 331 onLinkTab: onLinkTab,
348 style: style, 332 style: style,
349 ), 333 ),
350 - ),  
351 - ],  
352 ); 334 );
353 } 335 }
354 336
@@ -377,26 +359,14 @@ class OrderedList extends BlockMd { @@ -377,26 +359,14 @@ class OrderedList extends BlockMd {
377 final void Function(String url, String title)? onLinkTab, 359 final void Function(String url, String title)? onLinkTab,
378 ) { 360 ) {
379 var match = exp.firstMatch(text.trim()); 361 var match = exp.firstMatch(text.trim());
380 - return Row(  
381 - crossAxisAlignment: CrossAxisAlignment.center,  
382 - mainAxisSize: MainAxisSize.max,  
383 - children: [  
384 - Padding(  
385 - padding: const EdgeInsets.symmetric(horizontal: 11),  
386 - child: Text(  
387 - "${match?[1]}",  
388 - style: (style ?? const TextStyle())  
389 - .copyWith(fontWeight: FontWeight.bold),  
390 - ),  
391 - ),  
392 - Expanded( 362 + return OrderedListView(
  363 + no: "${match?[1]}",
  364 + style: (style ?? const TextStyle()).copyWith(fontWeight: FontWeight.bold),
393 child: MdWidget( 365 child: MdWidget(
394 "${match?[2]}", 366 "${match?[2]}",
395 onLinkTab: onLinkTab, 367 onLinkTab: onLinkTab,
396 style: style, 368 style: style,
397 ), 369 ),
398 - ),  
399 - ],  
400 ); 370 );
401 } 371 }
402 372
@@ -576,6 +546,89 @@ class ImageMd extends InlineMd { @@ -576,6 +546,89 @@ class ImageMd extends InlineMd {
576 } 546 }
577 } 547 }
578 548
  549 +/// Table component
  550 +class TableMd extends BlockMd {
  551 + @override
  552 + Widget build(BuildContext context, String text, TextStyle? style,
  553 + void Function(String url, String title)? onLinkTab) {
  554 + final List<Map<int, String>> value = text
  555 + .split('\n')
  556 + .map<Map<int, String>>(
  557 + (e) => e
  558 + .split('|')
  559 + .where((element) => element.isNotEmpty)
  560 + .toList()
  561 + .asMap(),
  562 + )
  563 + .toList();
  564 + int maxCol = 0;
  565 + for (final each in value) {
  566 + if (maxCol < each.keys.length) {
  567 + maxCol = each.keys.length;
  568 + }
  569 + }
  570 + if (maxCol == 0) {
  571 + return Text("", style: style);
  572 + }
  573 + return Table(
  574 + defaultVerticalAlignment: TableCellVerticalAlignment.middle,
  575 + border: TableBorder.all(
  576 + width: 1,
  577 + color: Theme.of(context).colorScheme.onSurface,
  578 + ),
  579 + children: value
  580 + .map<TableRow>(
  581 + (e) => TableRow(
  582 + children: List.generate(
  583 + maxCol,
  584 + (index) => Center(
  585 + child: MdWidget(
  586 + (e[index] ?? "").trim(),
  587 + onLinkTab: onLinkTab,
  588 + style: style,
  589 + ),
  590 + ),
  591 + ),
  592 + ),
  593 + )
  594 + .toList(),
  595 + );
  596 + }
  597 +
  598 + @override
  599 + RegExp get exp => RegExp(
  600 + r"(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)?",
  601 + );
  602 +
  603 + @override
  604 + String toHtml(String text) {
  605 + final String value = text.trim().splitMapJoin(
  606 + RegExp(r'^\||\|\n\||\|$'),
  607 + onMatch: (p0) => "\n",
  608 + onNonMatch: (p0) {
  609 + if (p0.trim().isEmpty) {
  610 + return "";
  611 + }
  612 + // return p0;
  613 + return '<tr>${p0.trim().splitMapJoin(
  614 + '|',
  615 + onMatch: (p0) {
  616 + return "";
  617 + },
  618 + onNonMatch: (p0) {
  619 + return '<td>$p0</td>';
  620 + },
  621 + )}</tr>';
  622 + },
  623 + );
  624 + return '''
  625 +<table border="1" cellspacing="0">
  626 +$value
  627 +</table>
  628 +''';
  629 + }
  630 +}
  631 +
579 /// Text component 632 /// Text component
580 class TextMd extends InlineMd { 633 class TextMd extends InlineMd {
581 @override 634 @override
  1 +import 'dart:math';
  2 +
1 import 'package:flutter/material.dart'; 3 import 'package:flutter/material.dart';
2 import 'package:tex_markdown/markdown_component.dart'; 4 import 'package:tex_markdown/markdown_component.dart';
3 5
@@ -46,11 +48,21 @@ $value @@ -46,11 +48,21 @@ $value
46 48
47 @override 49 @override
48 Widget build(BuildContext context) { 50 Widget build(BuildContext context) {
  51 + List<InlineSpan> list = [];
  52 + exp.trim().splitMapJoin(
  53 + RegExp(r"\n\n+"),
  54 + onMatch: (p0) {
  55 + list.add(
  56 + const TextSpan(text: "\n"),
  57 + );
  58 + return "";
  59 + },
  60 + onNonMatch: (eachLn) {
49 final RegExp table = RegExp( 61 final RegExp table = RegExp(
50 r"^(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)?$", 62 r"^(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)?$",
51 ); 63 );
52 - if (table.hasMatch(exp)) {  
53 - final List<Map<int, String>> value = exp 64 + if (table.hasMatch(eachLn)) {
  65 + final List<Map<int, String>> value = eachLn
54 .split('\n') 66 .split('\n')
55 .map<Map<int, String>>( 67 .map<Map<int, String>>(
56 (e) => e 68 (e) => e
@@ -66,11 +78,20 @@ $value @@ -66,11 +78,20 @@ $value
66 maxCol = each.keys.length; 78 maxCol = each.keys.length;
67 } 79 }
68 } 80 }
69 - if (maxCol == 0) {  
70 - return Text("", style: style);  
71 - }  
72 - return Table( 81 + // if (maxCol == 0) {
  82 + // return Text("", style: style);
  83 + // }
  84 + list.addAll(
  85 + [
  86 + const TextSpan(
  87 + text: "\n",
  88 + style: TextStyle(height: 0),
  89 + ),
  90 + WidgetSpan(
  91 + child: Table(
  92 + defaultColumnWidth: CustomTableColumnWidth(),
73 defaultVerticalAlignment: TableCellVerticalAlignment.middle, 93 defaultVerticalAlignment: TableCellVerticalAlignment.middle,
  94 + // defaultColumnWidth: const FixedColumnWidth(double.infinity),
74 border: TableBorder.all( 95 border: TableBorder.all(
75 width: 1, 96 width: 1,
76 color: Theme.of(context).colorScheme.onSurface, 97 color: Theme.of(context).colorScheme.onSurface,
@@ -91,8 +112,46 @@ $value @@ -91,8 +112,46 @@ $value
91 ), 112 ),
92 ) 113 )
93 .toList(), 114 .toList(),
  115 + ),
  116 + ),
  117 + const TextSpan(
  118 + text: "\n",
  119 + style: TextStyle(height: 0),
  120 + ),
  121 + ],
  122 + );
  123 + } else {
  124 + list.addAll(
  125 + MarkdownComponent.generate(context, eachLn, style, onLinkTab),
94 ); 126 );
95 } 127 }
96 - return MarkdownComponent.generate(context, exp, style, onLinkTab); 128 + return "";
  129 + },
  130 + );
  131 + return Text.rich(
  132 + TextSpan(
  133 + children: list,
  134 + ),
  135 + );
  136 + }
  137 +}
  138 +
  139 +class CustomTableColumnWidth extends TableColumnWidth {
  140 + @override
  141 + double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
  142 + double width = 50;
  143 + for (var each in cells) {
  144 + each.layout(const BoxConstraints(), parentUsesSize: true);
  145 + width = max(width, each.size.width);
  146 + }
  147 + if (containerWidth == double.infinity) {
  148 + return width;
  149 + }
  150 + return min(containerWidth, width);
  151 + }
  152 +
  153 + @override
  154 + double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
  155 + return 50;
97 } 156 }
98 } 157 }
@@ -27,25 +27,6 @@ class TexMarkdown extends StatelessWidget { @@ -27,25 +27,6 @@ class TexMarkdown extends StatelessWidget {
27 27
28 @override 28 @override
29 Widget build(BuildContext context) { 29 Widget build(BuildContext context) {
30 - return Column(  
31 - crossAxisAlignment: CrossAxisAlignment.start,  
32 - children: data  
33 - .trim()  
34 - .split(  
35 - RegExp(r"\n\n+"),  
36 - )  
37 - .map<Widget>(  
38 - (e) => Padding(  
39 - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 4),  
40 - child: MdWidget(  
41 - e,  
42 - style: style,  
43 - followLinkColor: followLinkColor,  
44 - onLinkTab: onLinkTab,  
45 - ),  
46 - ),  
47 - )  
48 - .toList(),  
49 - ); 30 + return ClipRRect(child: MdWidget(data.trim()));
50 } 31 }
51 } 32 }