saminsohag

Code block added

1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
  2 +import 'package:flutter/services.dart';
2 import 'package:flutter_math_fork/flutter_math.dart'; 3 import 'package:flutter_math_fork/flutter_math.dart';
3 import 'package:gpt_markdown/custom_widgets/custom_divider.dart'; 4 import 'package:gpt_markdown/custom_widgets/custom_divider.dart';
4 import 'package:gpt_markdown/custom_widgets/custom_error_image.dart'; 5 import 'package:gpt_markdown/custom_widgets/custom_error_image.dart';
@@ -9,6 +10,9 @@ import 'md_widget.dart'; @@ -9,6 +10,9 @@ import 'md_widget.dart';
9 /// Markdown components 10 /// Markdown components
10 abstract class MarkdownComponent { 11 abstract class MarkdownComponent {
11 static List<MarkdownComponent> get components => [ 12 static List<MarkdownComponent> get components => [
  13 + CodeBlockMd(),
  14 + NewLines(),
  15 + TableMd(),
12 HTag(), 16 HTag(),
13 IndentMd(), 17 IndentMd(),
14 UnOrderedList(), 18 UnOrderedList(),
@@ -251,6 +255,29 @@ class HTag extends BlockMd { @@ -251,6 +255,29 @@ class HTag extends BlockMd {
251 } 255 }
252 } 256 }
253 257
  258 +class NewLines extends InlineMd {
  259 + @override
  260 + RegExp get exp => RegExp(r"\n\n+");
  261 + @override
  262 + InlineSpan span(
  263 + BuildContext context,
  264 + String text,
  265 + TextStyle? style,
  266 + TextDirection textDirection,
  267 + final void Function(String url, String title)? onLinkTab,
  268 + final String Function(String tex)? latexWorkaround,
  269 + final Widget Function(BuildContext context, String tex)? latexBuilder,
  270 + ) {
  271 + return TextSpan(
  272 + text: "\n\n\n\n",
  273 + style: TextStyle(
  274 + fontSize: 6,
  275 + color: style?.color,
  276 + ),
  277 + );
  278 + }
  279 +}
  280 +
254 /// Horizontal line component 281 /// Horizontal line component
255 class HrLine extends BlockMd { 282 class HrLine extends BlockMd {
256 @override 283 @override
@@ -760,6 +787,10 @@ class TableMd extends BlockMd { @@ -760,6 +787,10 @@ class TableMd extends BlockMd {
760 .asMap(), 787 .asMap(),
761 ) 788 )
762 .toList(); 789 .toList();
  790 + bool heading = RegExp(
  791 + r"^\|.*?\|\n\|-[-\\ |]*?-\|$",
  792 + multiLine: true,
  793 + ).hasMatch(text.trim());
763 int maxCol = 0; 794 int maxCol = 0;
764 for (final each in value) { 795 for (final each in value) {
765 if (maxCol < each.keys.length) { 796 if (maxCol < each.keys.length) {
@@ -770,26 +801,50 @@ class TableMd extends BlockMd { @@ -770,26 +801,50 @@ class TableMd extends BlockMd {
770 return Text("", style: style); 801 return Text("", style: style);
771 } 802 }
772 return Table( 803 return Table(
773 - defaultVerticalAlignment: TableCellVerticalAlignment.middle,  
774 textDirection: textDirection, 804 textDirection: textDirection,
  805 + defaultColumnWidth: CustomTableColumnWidth(),
  806 + defaultVerticalAlignment: TableCellVerticalAlignment.middle,
775 border: TableBorder.all( 807 border: TableBorder.all(
776 width: 1, 808 width: 1,
777 color: Theme.of(context).colorScheme.onSurface, 809 color: Theme.of(context).colorScheme.onSurface,
778 ), 810 ),
779 children: value 811 children: value
  812 + .asMap()
  813 + .entries
780 .map<TableRow>( 814 .map<TableRow>(
781 - (e) => TableRow( 815 + (entry) => TableRow(
  816 + decoration: (heading)
  817 + ? BoxDecoration(
  818 + color: (entry.key == 0)
  819 + ? Theme.of(context).colorScheme.surfaceVariant
  820 + : null,
  821 + )
  822 + : null,
782 children: List.generate( 823 children: List.generate(
783 maxCol, 824 maxCol,
784 - (index) => Center(  
785 - child: MdWidget(  
786 - (e[index] ?? "").trim(),  
787 - textDirection: textDirection,  
788 - onLinkTab: onLinkTab,  
789 - style: style,  
790 - latexWorkaround: latexWorkaround,  
791 - ),  
792 - ), 825 + (index) {
  826 + var e = entry.value;
  827 + String data = e[index] ?? "";
  828 + if (RegExp(r"^--+$").hasMatch(data.trim()) ||
  829 + data.trim().isEmpty) {
  830 + return const SizedBox();
  831 + }
  832 +
  833 + return Center(
  834 + child: Padding(
  835 + padding: const EdgeInsets.symmetric(
  836 + horizontal: 8, vertical: 4),
  837 + child: MdWidget(
  838 + (e[index] ?? "").trim(),
  839 + textDirection: textDirection,
  840 + onLinkTab: onLinkTab,
  841 + style: style,
  842 + latexWorkaround: latexWorkaround,
  843 + latexBuilder: latexBuilder,
  844 + ),
  845 + ),
  846 + );
  847 + },
793 ), 848 ),
794 ), 849 ),
795 ) 850 )
@@ -799,6 +854,105 @@ class TableMd extends BlockMd { @@ -799,6 +854,105 @@ class TableMd extends BlockMd {
799 854
800 @override 855 @override
801 RegExp get exp => RegExp( 856 RegExp get exp => RegExp(
802 - r"(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)?", 857 + r"^(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)$",
  858 + );
  859 +}
  860 +
  861 +class CodeBlockMd extends BlockMd {
  862 + @override
  863 + RegExp get exp => RegExp(
  864 + r"\s*?```(.*?)\n((.*?)(:?\n\s*?```)|(.*)(:?\n```)?)$",
  865 + multiLine: true,
  866 + dotAll: true,
803 ); 867 );
  868 + @override
  869 + Widget build(
  870 + BuildContext context,
  871 + String text,
  872 + TextStyle? style,
  873 + TextDirection textDirection,
  874 + final void Function(String url, String title)? onLinkTab,
  875 + final String Function(String tex)? latexWorkaround,
  876 + final Widget Function(BuildContext context, String tex)? latexBuilder,
  877 + ) {
  878 + String codes = exp.firstMatch(text)?[2] ?? "";
  879 + String name = exp.firstMatch(text)?[1] ?? "";
  880 + codes = codes.replaceAll(r"```", "").trim();
  881 + return Padding(
  882 + padding: const EdgeInsets.all(16.0),
  883 + child: CodeField(name: name, codes: codes),
  884 + );
  885 + }
  886 +}
  887 +
  888 +class CodeField extends StatefulWidget {
  889 + const CodeField({super.key, required this.name, required this.codes});
  890 + final String name;
  891 + final String codes;
  892 +
  893 + @override
  894 + State<CodeField> createState() => _CodeFieldState();
  895 +}
  896 +
  897 +class _CodeFieldState extends State<CodeField> {
  898 + bool _copied = false;
  899 + @override
  900 + Widget build(BuildContext context) {
  901 + return Material(
  902 + color: Theme.of(context).colorScheme.onInverseSurface,
  903 + shape: RoundedRectangleBorder(
  904 + borderRadius: BorderRadius.circular(8),
  905 + ),
  906 + child: Column(
  907 + crossAxisAlignment: CrossAxisAlignment.stretch,
  908 + children: [
  909 + Row(
  910 + children: [
  911 + Padding(
  912 + padding:
  913 + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
  914 + child: Text(widget.name),
  915 + ),
  916 + const Spacer(),
  917 + TextButton.icon(
  918 + style: TextButton.styleFrom(
  919 + foregroundColor: Theme.of(context).colorScheme.onSurface,
  920 + textStyle: const TextStyle(
  921 + fontWeight: FontWeight.normal,
  922 + ),
  923 + ),
  924 + onPressed: () async {
  925 + await Clipboard.setData(ClipboardData(text: widget.codes))
  926 + .then((value) {
  927 + setState(() {
  928 + _copied = true;
  929 + });
  930 + });
  931 + await Future.delayed(const Duration(seconds: 2));
  932 + setState(() {
  933 + _copied = false;
  934 + });
  935 + },
  936 + icon: Icon(
  937 + (_copied) ? Icons.done : Icons.content_paste,
  938 + size: 15,
  939 + ),
  940 + label: Text((_copied) ? "Copied!" : "Copy code"),
  941 + ),
  942 + ],
  943 + ),
  944 + const Divider(
  945 + height: 1,
  946 + ),
  947 + SingleChildScrollView(
  948 + scrollDirection: Axis.horizontal,
  949 + padding: const EdgeInsets.all(16),
  950 + child: Text(
  951 + widget.codes,
  952 + ),
  953 + ),
  954 + ],
  955 + ),
  956 + );
  957 + }
804 } 958 }
@@ -28,137 +28,25 @@ class MdWidget extends StatelessWidget { @@ -28,137 +28,25 @@ class MdWidget extends StatelessWidget {
28 @override 28 @override
29 Widget build(BuildContext context) { 29 Widget build(BuildContext context) {
30 List<InlineSpan> list = []; 30 List<InlineSpan> list = [];
31 - exp.trim().splitMapJoin(  
32 - RegExp(r"\n\n+"),  
33 - onMatch: (p0) {  
34 - list.add(  
35 - TextSpan(  
36 - text: "\n\n\n\n",  
37 - style: TextStyle(  
38 - fontSize: 6,  
39 - color: style?.color,  
40 - ),  
41 - ),  
42 - );  
43 - return "";  
44 - },  
45 - onNonMatch: (eachLn) {  
46 - final RegExp table = RegExp(  
47 - r"^(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)$",  
48 - );  
49 - if (table.hasMatch(eachLn.trim())) {  
50 - final List<Map<int, String>> value = eachLn  
51 - .trim()  
52 - .split('\n')  
53 - .map<Map<int, String>>(  
54 - (e) => e  
55 - .split('|')  
56 - .where((element) => element.isNotEmpty)  
57 - .toList()  
58 - .asMap(),  
59 - )  
60 - .toList();  
61 - bool heading = RegExp(  
62 - r"^\|.*?\|\n\|-[-\\ |]*?-\|$",  
63 - multiLine: true,  
64 - ).hasMatch(eachLn.trim());  
65 - int maxCol = 0;  
66 - for (final each in value) {  
67 - if (maxCol < each.keys.length) {  
68 - maxCol = each.keys.length;  
69 - }  
70 - }  
71 - list.addAll(  
72 - [  
73 - TextSpan(  
74 - text: "\n ",  
75 - style: TextStyle(height: 0, fontSize: 0, color: style?.color),  
76 - ),  
77 - WidgetSpan(  
78 - child: Table(  
79 - textDirection: textDirection,  
80 - defaultColumnWidth: CustomTableColumnWidth(),  
81 - defaultVerticalAlignment: TableCellVerticalAlignment.middle,  
82 - border: TableBorder.all(  
83 - width: 1,  
84 - color: Theme.of(context).colorScheme.onSurface,  
85 - ),  
86 - children: value  
87 - .asMap()  
88 - .entries  
89 - .map<TableRow>(  
90 - (entry) => TableRow(  
91 - decoration: (heading)  
92 - ? BoxDecoration(  
93 - color: (entry.key == 0)  
94 - ? Theme.of(context)  
95 - .colorScheme  
96 - .surfaceVariant  
97 - : null,  
98 - )  
99 - : null,  
100 - children: List.generate(  
101 - maxCol,  
102 - (index) {  
103 - var e = entry.value;  
104 - String data = e[index] ?? "";  
105 - if (RegExp(r"^--+$").hasMatch(data.trim()) ||  
106 - data.trim().isEmpty) {  
107 - return const SizedBox();  
108 - }  
109 -  
110 - return Center(  
111 - child: Padding(  
112 - padding: const EdgeInsets.symmetric(  
113 - horizontal: 8, vertical: 4),  
114 - child: MdWidget(  
115 - (e[index] ?? "").trim(),  
116 - textDirection: textDirection,  
117 - onLinkTab: onLinkTab,  
118 - style: style,  
119 - latexWorkaround: latexWorkaround,  
120 - latexBuilder: latexBuilder,  
121 - ),  
122 - ),  
123 - );  
124 - },  
125 - ),  
126 - ),  
127 - )  
128 - .toList(),  
129 - ),  
130 - ),  
131 - TextSpan(  
132 - text: "\n ",  
133 - style: TextStyle(height: 0, fontSize: 0, color: style?.color),  
134 - ),  
135 - ],  
136 - );  
137 - } else {  
138 - list.addAll(  
139 - MarkdownComponent.generate(  
140 - context,  
141 - eachLn.replaceAllMapped(  
142 - RegExp(  
143 - r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})",  
144 - multiLine: true,  
145 - dotAll: true,  
146 - ), (match) {  
147 - //  
148 - String body =  
149 - (match[1] ?? match[2])?.replaceAll("\n", " ") ?? "";  
150 - return "\\[$body\\]";  
151 - }),  
152 - style,  
153 - textDirection,  
154 - onLinkTab,  
155 - latexWorkaround,  
156 - latexBuilder,  
157 - ),  
158 - );  
159 - }  
160 - return "";  
161 - }, 31 + list.addAll(
  32 + MarkdownComponent.generate(
  33 + context,
  34 + exp.replaceAllMapped(
  35 + RegExp(
  36 + r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})",
  37 + multiLine: true,
  38 + dotAll: true,
  39 + ), (match) {
  40 + //
  41 + String body = (match[1] ?? match[2])?.replaceAll("\n", " ") ?? "";
  42 + return "\\[$body\\]";
  43 + }),
  44 + style,
  45 + textDirection,
  46 + onLinkTab,
  47 + latexWorkaround,
  48 + latexBuilder,
  49 + ),
162 ); 50 );
163 return Text.rich( 51 return Text.rich(
164 TextSpan( 52 TextSpan(