saminsohag

new release

  1 +## 1.0.0
  2 +
  3 +* `TexMarkdown` is renamed to `GptMarkdown`.
  4 +* `h1` to `h6` style added to `GptMarkdownThemeData` class.
  5 +* `hrLineThickness` value added to `GptMarkdownThemeData` class.
  6 +* `hrLineColor` Color added to `GptMarkdownThemeData` class.
  7 +* `linkColor` Color added to `GptMarkdownThemeData` class.
  8 +* `linkHoverColor` Color added to `GptMarkdownThemeData` class.
  9 +* Indentation improved.
  10 +* Math equations are now default selectable.
  11 +* `SelectableAdapter` Widget added to make any widget selectable.
  12 +
  13 +## 0.1.15
  14 +
  15 +* `CodeBlock` is moved out of `gpt_markdown.dart` library.
  16 +
1 ## 0.1.14 17 ## 0.1.14
2 18
3 -* Cnaged `withOpacity` to `withAlpha` in `theme.dart` for highlightColor. 19 +* Changed `withOpacity` to `withAlpha` in `theme.dart` for highlightColor.
4 20
5 ## 0.1.13 21 ## 0.1.13
6 22
@@ -81,7 +81,7 @@ Check the documentation [here.](https://github.com/saminsohag/flutter_packages/t @@ -81,7 +81,7 @@ Check the documentation [here.](https://github.com/saminsohag/flutter_packages/t
81 import 'package:flutter/material.dart'; 81 import 'package:flutter/material.dart';
82 import 'package:gpt_markdown/gpt_markdown.dart'; 82 import 'package:gpt_markdown/gpt_markdown.dart';
83 83
84 -return TexMarkdown( 84 +return GptMarkdown(
85 ''' 85 '''
86 * This is a unordered list. 86 * This is a unordered list.
87 ''', 87 ''',
@@ -5,9 +5,11 @@ @@ -5,9 +5,11 @@
5 *.swp 5 *.swp
6 .DS_Store 6 .DS_Store
7 .atom/ 7 .atom/
  8 +.build/
8 .buildlog/ 9 .buildlog/
9 .history 10 .history
10 .svn/ 11 .svn/
  12 +.swiftpm/
11 migrate_working_dir/ 13 migrate_working_dir/
12 14
13 # IntelliJ related 15 # IntelliJ related
@@ -48,7 +48,7 @@ class _MyHomePageState extends State<MyHomePage> { @@ -48,7 +48,7 @@ class _MyHomePageState extends State<MyHomePage> {
48 AnimatedBuilder( 48 AnimatedBuilder(
49 animation: _controller, 49 animation: _controller,
50 builder: (context, _) { 50 builder: (context, _) {
51 - return TexMarkdown( 51 + return GptMarkdown(
52 _controller.text, 52 _controller.text,
53 style: const TextStyle( 53 style: const TextStyle(
54 color: Colors.red, 54 color: Colors.red,
@@ -30,6 +30,12 @@ class _MyAppState extends State<MyApp> { @@ -30,6 +30,12 @@ class _MyAppState extends State<MyApp> {
30 useMaterial3: true, 30 useMaterial3: true,
31 brightness: Brightness.light, 31 brightness: Brightness.light,
32 colorSchemeSeed: Colors.blue, 32 colorSchemeSeed: Colors.blue,
  33 + extensions: [
  34 + GptMarkdownThemeData(
  35 + brightness: Brightness.light,
  36 + highlightColor: Colors.red,
  37 + ),
  38 + ],
33 ), 39 ),
34 darkTheme: ThemeData( 40 darkTheme: ThemeData(
35 useMaterial3: true, 41 useMaterial3: true,
@@ -37,9 +43,11 @@ class _MyAppState extends State<MyApp> { @@ -37,9 +43,11 @@ class _MyAppState extends State<MyApp> {
37 colorSchemeSeed: Colors.blue, 43 colorSchemeSeed: Colors.blue,
38 extensions: [ 44 extensions: [
39 GptMarkdownThemeData( 45 GptMarkdownThemeData(
  46 + brightness: Brightness.dark,
40 highlightColor: Colors.red, 47 highlightColor: Colors.red,
41 ), 48 ),
42 - ]), 49 + ],
  50 + ),
43 home: MyHomePage( 51 home: MyHomePage(
44 title: 'GptMarkdown', 52 title: 'GptMarkdown',
45 onPressed: () { 53 onPressed: () {
@@ -170,7 +178,10 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex @@ -170,7 +178,10 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
170 Icons.select_all_outlined, 178 Icons.select_all_outlined,
171 color: selectable 179 color: selectable
172 ? Theme.of(context).colorScheme.onSurfaceVariant 180 ? Theme.of(context).colorScheme.onSurfaceVariant
173 - : Theme.of(context).colorScheme.onSurface.withOpacity(0.38), 181 + : Theme.of(context)
  182 + .colorScheme
  183 + .onSurface
  184 + .withValues(alpha: 0.38),
174 ), 185 ),
175 ), 186 ),
176 IconButton( 187 IconButton(
@@ -243,7 +254,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex @@ -243,7 +254,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
243 // ), 254 // ),
244 child: Builder( 255 child: Builder(
245 builder: (context) { 256 builder: (context) {
246 - Widget child = TexMarkdown( 257 + Widget child = GptMarkdown(
247 _controller.text, 258 _controller.text,
248 textDirection: _direction, 259 textDirection: _direction,
249 onLinkTab: (url, title) { 260 onLinkTab: (url, title) {
@@ -301,7 +312,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex @@ -301,7 +312,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
301 ..insert(1, "|---|"); 312 ..insert(1, "|---|");
302 tableString = 313 tableString =
303 tableStringList.join("\n"); 314 tableStringList.join("\n");
304 - return TexMarkdown(tableString); 315 + return GptMarkdown(tableString);
305 } 316 }
306 var controller = ScrollController(); 317 var controller = ScrollController();
307 Widget child = Math.tex( 318 Widget child = Math.tex(
@@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate { @@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate {
6 override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 6 override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 return true 7 return true
8 } 8 }
  9 +
  10 + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
  11 + return true
  12 + }
9 } 13 }
@@ -5,10 +5,10 @@ packages: @@ -5,10 +5,10 @@ packages:
5 dependency: transitive 5 dependency: transitive
6 description: 6 description:
7 name: args 7 name: args
8 - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" 8 + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
9 url: "https://pub.dev" 9 url: "https://pub.dev"
10 source: hosted 10 source: hosted
11 - version: "2.5.0" 11 + version: "2.6.0"
12 async: 12 async:
13 dependency: transitive 13 dependency: transitive
14 description: 14 description:
@@ -45,10 +45,10 @@ packages: @@ -45,10 +45,10 @@ packages:
45 dependency: transitive 45 dependency: transitive
46 description: 46 description:
47 name: collection 47 name: collection
48 - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 48 + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
49 url: "https://pub.dev" 49 url: "https://pub.dev"
50 source: hosted 50 source: hosted
51 - version: "1.18.0" 51 + version: "1.19.0"
52 cross_file: 52 cross_file:
53 dependency: transitive 53 dependency: transitive
54 description: 54 description:
@@ -98,18 +98,18 @@ packages: @@ -98,18 +98,18 @@ packages:
98 dependency: "direct main" 98 dependency: "direct main"
99 description: 99 description:
100 name: flutter_math_fork 100 name: flutter_math_fork
101 - sha256: "94bee4642892a94939af0748c6a7de0ff8318feee588379dcdfea7dc5cba06c8" 101 + sha256: "284bab89b2fbf1bc3a0baf13d011c1dd324d004e35d177626b77f2fc056366ac"
102 url: "https://pub.dev" 102 url: "https://pub.dev"
103 source: hosted 103 source: hosted
104 - version: "0.7.2" 104 + version: "0.7.3"
105 flutter_svg: 105 flutter_svg:
106 dependency: transitive 106 dependency: transitive
107 description: 107 description:
108 name: flutter_svg 108 name: flutter_svg
109 - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" 109 + sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123"
110 url: "https://pub.dev" 110 url: "https://pub.dev"
111 source: hosted 111 source: hosted
112 - version: "2.0.10+1" 112 + version: "2.0.16"
113 flutter_test: 113 flutter_test:
114 dependency: "direct dev" 114 dependency: "direct dev"
115 description: flutter 115 description: flutter
@@ -126,7 +126,7 @@ packages: @@ -126,7 +126,7 @@ packages:
126 path: ".." 126 path: ".."
127 relative: true 127 relative: true
128 source: path 128 source: path
129 - version: "0.1.14" 129 + version: "1.0.0"
130 http: 130 http:
131 dependency: transitive 131 dependency: transitive
132 description: 132 description:
@@ -139,26 +139,26 @@ packages: @@ -139,26 +139,26 @@ packages:
139 dependency: transitive 139 dependency: transitive
140 description: 140 description:
141 name: http_parser 141 name: http_parser
142 - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" 142 + sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
143 url: "https://pub.dev" 143 url: "https://pub.dev"
144 source: hosted 144 source: hosted
145 - version: "4.0.2" 145 + version: "4.1.1"
146 leak_tracker: 146 leak_tracker:
147 dependency: transitive 147 dependency: transitive
148 description: 148 description:
149 name: leak_tracker 149 name: leak_tracker
150 - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" 150 + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
151 url: "https://pub.dev" 151 url: "https://pub.dev"
152 source: hosted 152 source: hosted
153 - version: "10.0.5" 153 + version: "10.0.7"
154 leak_tracker_flutter_testing: 154 leak_tracker_flutter_testing:
155 dependency: transitive 155 dependency: transitive
156 description: 156 description:
157 name: leak_tracker_flutter_testing 157 name: leak_tracker_flutter_testing
158 - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" 158 + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
159 url: "https://pub.dev" 159 url: "https://pub.dev"
160 source: hosted 160 source: hosted
161 - version: "3.0.5" 161 + version: "3.0.8"
162 leak_tracker_testing: 162 leak_tracker_testing:
163 dependency: transitive 163 dependency: transitive
164 description: 164 description:
@@ -219,10 +219,10 @@ packages: @@ -219,10 +219,10 @@ packages:
219 dependency: transitive 219 dependency: transitive
220 description: 220 description:
221 name: path_parsing 221 name: path_parsing
222 - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf 222 + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
223 url: "https://pub.dev" 223 url: "https://pub.dev"
224 source: hosted 224 source: hosted
225 - version: "1.0.1" 225 + version: "1.1.0"
226 petitparser: 226 petitparser:
227 dependency: transitive 227 dependency: transitive
228 description: 228 description:
@@ -243,7 +243,7 @@ packages: @@ -243,7 +243,7 @@ packages:
243 dependency: transitive 243 dependency: transitive
244 description: flutter 244 description: flutter
245 source: sdk 245 source: sdk
246 - version: "0.0.99" 246 + version: "0.0.0"
247 source_span: 247 source_span:
248 dependency: transitive 248 dependency: transitive
249 description: 249 description:
@@ -256,10 +256,10 @@ packages: @@ -256,10 +256,10 @@ packages:
256 dependency: transitive 256 dependency: transitive
257 description: 257 description:
258 name: stack_trace 258 name: stack_trace
259 - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 259 + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
260 url: "https://pub.dev" 260 url: "https://pub.dev"
261 source: hosted 261 source: hosted
262 - version: "1.11.1" 262 + version: "1.12.0"
263 stream_channel: 263 stream_channel:
264 dependency: transitive 264 dependency: transitive
265 description: 265 description:
@@ -272,10 +272,10 @@ packages: @@ -272,10 +272,10 @@ packages:
272 dependency: transitive 272 dependency: transitive
273 description: 273 description:
274 name: string_scanner 274 name: string_scanner
275 - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 275 + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
276 url: "https://pub.dev" 276 url: "https://pub.dev"
277 source: hosted 277 source: hosted
278 - version: "1.2.0" 278 + version: "1.3.0"
279 term_glyph: 279 term_glyph:
280 dependency: transitive 280 dependency: transitive
281 description: 281 description:
@@ -288,10 +288,10 @@ packages: @@ -288,10 +288,10 @@ packages:
288 dependency: transitive 288 dependency: transitive
289 description: 289 description:
290 name: test_api 290 name: test_api
291 - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" 291 + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
292 url: "https://pub.dev" 292 url: "https://pub.dev"
293 source: hosted 293 source: hosted
294 - version: "0.7.2" 294 + version: "0.7.3"
295 tuple: 295 tuple:
296 dependency: transitive 296 dependency: transitive
297 description: 297 description:
@@ -304,34 +304,34 @@ packages: @@ -304,34 +304,34 @@ packages:
304 dependency: transitive 304 dependency: transitive
305 description: 305 description:
306 name: typed_data 306 name: typed_data
307 - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 307 + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
308 url: "https://pub.dev" 308 url: "https://pub.dev"
309 source: hosted 309 source: hosted
310 - version: "1.3.2" 310 + version: "1.4.0"
311 vector_graphics: 311 vector_graphics:
312 dependency: transitive 312 dependency: transitive
313 description: 313 description:
314 name: vector_graphics 314 name: vector_graphics
315 - sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" 315 + sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7"
316 url: "https://pub.dev" 316 url: "https://pub.dev"
317 source: hosted 317 source: hosted
318 - version: "1.1.11+1" 318 + version: "1.1.15"
319 vector_graphics_codec: 319 vector_graphics_codec:
320 dependency: transitive 320 dependency: transitive
321 description: 321 description:
322 name: vector_graphics_codec 322 name: vector_graphics_codec
323 - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da 323 + sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb"
324 url: "https://pub.dev" 324 url: "https://pub.dev"
325 source: hosted 325 source: hosted
326 - version: "1.1.11+1" 326 + version: "1.1.12"
327 vector_graphics_compiler: 327 vector_graphics_compiler:
328 dependency: transitive 328 dependency: transitive
329 description: 329 description:
330 name: vector_graphics_compiler 330 name: vector_graphics_compiler
331 - sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" 331 + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
332 url: "https://pub.dev" 332 url: "https://pub.dev"
333 source: hosted 333 source: hosted
334 - version: "1.1.11+1" 334 + version: "1.1.16"
335 vector_math: 335 vector_math:
336 dependency: transitive 336 dependency: transitive
337 description: 337 description:
@@ -344,26 +344,26 @@ packages: @@ -344,26 +344,26 @@ packages:
344 dependency: transitive 344 dependency: transitive
345 description: 345 description:
346 name: vm_service 346 name: vm_service
347 - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" 347 + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
348 url: "https://pub.dev" 348 url: "https://pub.dev"
349 source: hosted 349 source: hosted
350 - version: "14.2.5" 350 + version: "14.3.0"
351 watcher: 351 watcher:
352 dependency: "direct main" 352 dependency: "direct main"
353 description: 353 description:
354 name: watcher 354 name: watcher
355 - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" 355 + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
356 url: "https://pub.dev" 356 url: "https://pub.dev"
357 source: hosted 357 source: hosted
358 - version: "1.1.0" 358 + version: "1.1.1"
359 web: 359 web:
360 dependency: transitive 360 dependency: transitive
361 description: 361 description:
362 name: web 362 name: web
363 - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" 363 + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
364 url: "https://pub.dev" 364 url: "https://pub.dev"
365 source: hosted 365 source: hosted
366 - version: "0.5.1" 366 + version: "1.1.0"
367 xml: 367 xml:
368 dependency: transitive 368 dependency: transitive
369 description: 369 description:
@@ -374,4 +374,4 @@ packages: @@ -374,4 +374,4 @@ packages:
374 version: "6.5.0" 374 version: "6.5.0"
375 sdks: 375 sdks:
376 dart: ">=3.5.0 <4.0.0" 376 dart: ">=3.5.0 <4.0.0"
377 - flutter: ">=3.18.0-18.0.pre.54" 377 + flutter: ">=3.22.0"
  1 +import 'package:flutter/material.dart';
  2 +import 'package:flutter/services.dart';
  3 +
  4 +class CodeField extends StatefulWidget {
  5 + const CodeField({super.key, required this.name, required this.codes});
  6 + final String name;
  7 + final String codes;
  8 +
  9 + @override
  10 + State<CodeField> createState() => _CodeFieldState();
  11 +}
  12 +
  13 +class _CodeFieldState extends State<CodeField> {
  14 + bool _copied = false;
  15 + @override
  16 + Widget build(BuildContext context) {
  17 + return Material(
  18 + color: Theme.of(context).colorScheme.onInverseSurface,
  19 + shape: RoundedRectangleBorder(
  20 + borderRadius: BorderRadius.circular(8),
  21 + ),
  22 + child: Column(
  23 + crossAxisAlignment: CrossAxisAlignment.stretch,
  24 + children: [
  25 + Row(
  26 + children: [
  27 + Padding(
  28 + padding:
  29 + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
  30 + child: Text(widget.name),
  31 + ),
  32 + const Spacer(),
  33 + TextButton.icon(
  34 + style: TextButton.styleFrom(
  35 + foregroundColor: Theme.of(context).colorScheme.onSurface,
  36 + textStyle: const TextStyle(
  37 + fontWeight: FontWeight.normal,
  38 + ),
  39 + ),
  40 + onPressed: () async {
  41 + await Clipboard.setData(ClipboardData(text: widget.codes))
  42 + .then((value) {
  43 + setState(() {
  44 + _copied = true;
  45 + });
  46 + });
  47 + await Future.delayed(const Duration(seconds: 2));
  48 + setState(() {
  49 + _copied = false;
  50 + });
  51 + },
  52 + icon: Icon(
  53 + (_copied) ? Icons.done : Icons.content_paste,
  54 + size: 15,
  55 + ),
  56 + label: Text((_copied) ? "Copied!" : "Copy code"),
  57 + ),
  58 + ],
  59 + ),
  60 + const Divider(
  61 + height: 1,
  62 + ),
  63 + SingleChildScrollView(
  64 + scrollDirection: Axis.horizontal,
  65 + padding: const EdgeInsets.all(16),
  66 + child: Text(
  67 + widget.codes,
  68 + ),
  69 + ),
  70 + ],
  71 + ),
  72 + );
  73 + }
  74 +}
@@ -8,11 +8,15 @@ class LinkButton extends StatefulWidget { @@ -8,11 +8,15 @@ class LinkButton extends StatefulWidget {
8 final TextStyle? textStyle; 8 final TextStyle? textStyle;
9 final String? url; 9 final String? url;
10 final GptMarkdownConfig config; 10 final GptMarkdownConfig config;
  11 + final Color color;
  12 + final Color hoverColor;
11 13
12 const LinkButton( 14 const LinkButton(
13 {super.key, 15 {super.key,
14 required this.text, 16 required this.text,
15 required this.config, 17 required this.config,
  18 + required this.color,
  19 + required this.hoverColor,
16 this.onPressed, 20 this.onPressed,
17 this.textStyle, 21 this.textStyle,
18 this.url}); 22 this.url});
@@ -27,9 +31,9 @@ class _LinkButtonState extends State<LinkButton> { @@ -27,9 +31,9 @@ class _LinkButtonState extends State<LinkButton> {
27 @override 31 @override
28 Widget build(BuildContext context) { 32 Widget build(BuildContext context) {
29 var style = (widget.config.style ?? const TextStyle()).copyWith( 33 var style = (widget.config.style ?? const TextStyle()).copyWith(
30 - color: _isHovering ? Colors.red : Colors.blue, 34 + color: _isHovering ? widget.hoverColor : widget.color,
31 decoration: TextDecoration.underline, 35 decoration: TextDecoration.underline,
32 - decorationColor: _isHovering ? Colors.red : Colors.blue, 36 + decorationColor: _isHovering ? widget.hoverColor : widget.color,
33 ); 37 );
34 return MouseRegion( 38 return MouseRegion(
35 cursor: SystemMouseCursors.click, 39 cursor: SystemMouseCursors.click,
  1 +import 'package:flutter/material.dart';
  2 +import 'package:flutter/rendering.dart';
  3 +
  4 +class SelectableAdapter extends StatelessWidget {
  5 + const SelectableAdapter(
  6 + {super.key, required this.selectedText, required this.child});
  7 +
  8 + final Widget child;
  9 + final String selectedText;
  10 +
  11 + @override
  12 + Widget build(BuildContext context) {
  13 + final SelectionRegistrar? registrar = SelectionContainer.maybeOf(context);
  14 + if (registrar == null) {
  15 + return child;
  16 + }
  17 + return MouseRegion(
  18 + cursor: SystemMouseCursors.text,
  19 + child: _SelectableAdapter(
  20 + registrar: registrar,
  21 + selectedText: selectedText,
  22 + child: child,
  23 + ),
  24 + );
  25 + }
  26 +}
  27 +
  28 +class _SelectableAdapter extends SingleChildRenderObjectWidget {
  29 + const _SelectableAdapter({
  30 + required this.registrar,
  31 + required Widget child,
  32 + required this.selectedText,
  33 + }) : super(child: child);
  34 +
  35 + final SelectionRegistrar registrar;
  36 + final String selectedText;
  37 +
  38 + @override
  39 + _RenderSelectableAdapter createRenderObject(BuildContext context) {
  40 + return _RenderSelectableAdapter(
  41 + DefaultSelectionStyle.of(context).selectionColor!,
  42 + selectedText,
  43 + registrar,
  44 + );
  45 + }
  46 +
  47 + @override
  48 + void updateRenderObject(
  49 + BuildContext context, _RenderSelectableAdapter renderObject) {
  50 + renderObject
  51 + ..selectionColor = DefaultSelectionStyle.of(context).selectionColor!
  52 + ..registrar = registrar;
  53 + }
  54 +}
  55 +
  56 +class _RenderSelectableAdapter extends RenderProxyBox
  57 + with Selectable, SelectionRegistrant {
  58 + String selectionText;
  59 + _RenderSelectableAdapter(
  60 + Color selectionColor,
  61 + this.selectionText,
  62 + SelectionRegistrar registrar,
  63 + ) : _selectionColor = selectionColor,
  64 + _geometry = ValueNotifier<SelectionGeometry>(_noSelection) {
  65 + this.registrar = registrar;
  66 + _geometry.addListener(markNeedsPaint);
  67 + }
  68 +
  69 + static const SelectionGeometry _noSelection =
  70 + SelectionGeometry(status: SelectionStatus.none, hasContent: true);
  71 + final ValueNotifier<SelectionGeometry> _geometry;
  72 +
  73 + Color get selectionColor => _selectionColor;
  74 + late Color _selectionColor;
  75 + set selectionColor(Color value) {
  76 + if (_selectionColor == value) {
  77 + return;
  78 + }
  79 + _selectionColor = value;
  80 + markNeedsPaint();
  81 + }
  82 +
  83 + // ValueListenable APIs
  84 +
  85 + @override
  86 + void addListener(VoidCallback listener) => _geometry.addListener(listener);
  87 +
  88 + @override
  89 + void removeListener(VoidCallback listener) =>
  90 + _geometry.removeListener(listener);
  91 +
  92 + @override
  93 + SelectionGeometry get value => _geometry.value;
  94 +
  95 + // Selectable APIs.
  96 +
  97 + @override
  98 + List<Rect> get boundingBoxes => <Rect>[paintBounds];
  99 +
  100 + // Adjust this value to enlarge or shrink the selection highlight.
  101 + static const double _padding = 0.0;
  102 + Rect _getSelectionHighlightRect() {
  103 + return Rect.fromLTWH(0 - _padding, 0 - _padding, size.width + _padding * 2,
  104 + size.height + _padding * 2);
  105 + }
  106 +
  107 + Offset? _start;
  108 + Offset? _end;
  109 + void _updateGeometry() {
  110 + if (_start == null || _end == null) {
  111 + _geometry.value = _noSelection;
  112 + return;
  113 + }
  114 + final Rect renderObjectRect = Rect.fromLTWH(0, 0, size.width, size.height);
  115 + final Rect selectionRect = Rect.fromPoints(_start!, _end!);
  116 + if (renderObjectRect.intersect(selectionRect).isEmpty) {
  117 + _geometry.value = _noSelection;
  118 + } else {
  119 + final Rect selectionRect = _getSelectionHighlightRect();
  120 + final SelectionPoint firstSelectionPoint = SelectionPoint(
  121 + localPosition: selectionRect.bottomLeft,
  122 + lineHeight: selectionRect.size.height,
  123 + handleType: TextSelectionHandleType.left,
  124 + );
  125 + final SelectionPoint secondSelectionPoint = SelectionPoint(
  126 + localPosition: selectionRect.bottomRight,
  127 + lineHeight: selectionRect.size.height,
  128 + handleType: TextSelectionHandleType.right,
  129 + );
  130 + final bool isReversed;
  131 + if (_start!.dy > _end!.dy) {
  132 + isReversed = true;
  133 + } else if (_start!.dy < _end!.dy) {
  134 + isReversed = false;
  135 + } else {
  136 + isReversed = _start!.dx > _end!.dx;
  137 + }
  138 + _geometry.value = SelectionGeometry(
  139 + status: SelectionStatus.uncollapsed,
  140 + hasContent: true,
  141 + startSelectionPoint:
  142 + isReversed ? secondSelectionPoint : firstSelectionPoint,
  143 + endSelectionPoint:
  144 + isReversed ? firstSelectionPoint : secondSelectionPoint,
  145 + selectionRects: <Rect>[selectionRect],
  146 + );
  147 + }
  148 + }
  149 +
  150 + @override
  151 + SelectionResult dispatchSelectionEvent(SelectionEvent event) {
  152 + SelectionResult result = SelectionResult.none;
  153 + switch (event.type) {
  154 + case SelectionEventType.startEdgeUpdate:
  155 + case SelectionEventType.endEdgeUpdate:
  156 + final Rect renderObjectRect =
  157 + Rect.fromLTWH(0, 0, size.width, size.height);
  158 + // Normalize offset in case it is out side of the rect.
  159 + final Offset point =
  160 + globalToLocal((event as SelectionEdgeUpdateEvent).globalPosition);
  161 + final Offset adjustedPoint =
  162 + SelectionUtils.adjustDragOffset(renderObjectRect, point);
  163 + if (event.type == SelectionEventType.startEdgeUpdate) {
  164 + _start = adjustedPoint;
  165 + } else {
  166 + _end = adjustedPoint;
  167 + }
  168 + result = SelectionUtils.getResultBasedOnRect(renderObjectRect, point);
  169 + case SelectionEventType.clear:
  170 + _start = _end = null;
  171 + case SelectionEventType.selectAll:
  172 + case SelectionEventType.selectWord:
  173 + case SelectionEventType.selectParagraph:
  174 + _start = Offset.zero;
  175 + _end = Offset.infinite;
  176 + case SelectionEventType.granularlyExtendSelection:
  177 + result = SelectionResult.end;
  178 + final GranularlyExtendSelectionEvent extendSelectionEvent =
  179 + event as GranularlyExtendSelectionEvent;
  180 + // Initialize the offset it there is no ongoing selection.
  181 + if (_start == null || _end == null) {
  182 + if (extendSelectionEvent.forward) {
  183 + _start = _end = Offset.zero;
  184 + } else {
  185 + _start = _end = Offset.infinite;
  186 + }
  187 + }
  188 + // Move the corresponding selection edge.
  189 + final Offset newOffset =
  190 + extendSelectionEvent.forward ? Offset.infinite : Offset.zero;
  191 + if (extendSelectionEvent.isEnd) {
  192 + if (newOffset == _end) {
  193 + result = extendSelectionEvent.forward
  194 + ? SelectionResult.next
  195 + : SelectionResult.previous;
  196 + }
  197 + _end = newOffset;
  198 + } else {
  199 + if (newOffset == _start) {
  200 + result = extendSelectionEvent.forward
  201 + ? SelectionResult.next
  202 + : SelectionResult.previous;
  203 + }
  204 + _start = newOffset;
  205 + }
  206 + case SelectionEventType.directionallyExtendSelection:
  207 + result = SelectionResult.end;
  208 + final DirectionallyExtendSelectionEvent extendSelectionEvent =
  209 + event as DirectionallyExtendSelectionEvent;
  210 + // Convert to local coordinates.
  211 + final double horizontalBaseLine = globalToLocal(Offset(event.dx, 0)).dx;
  212 + final Offset newOffset;
  213 + final bool forward;
  214 + switch (extendSelectionEvent.direction) {
  215 + case SelectionExtendDirection.backward:
  216 + case SelectionExtendDirection.previousLine:
  217 + forward = false;
  218 + // Initialize the offset it there is no ongoing selection.
  219 + if (_start == null || _end == null) {
  220 + _start = _end = Offset.infinite;
  221 + }
  222 + // Move the corresponding selection edge.
  223 + if (extendSelectionEvent.direction ==
  224 + SelectionExtendDirection.previousLine ||
  225 + horizontalBaseLine < 0) {
  226 + newOffset = Offset.zero;
  227 + } else {
  228 + newOffset = Offset.infinite;
  229 + }
  230 + case SelectionExtendDirection.nextLine:
  231 + case SelectionExtendDirection.forward:
  232 + forward = true;
  233 + // Initialize the offset it there is no ongoing selection.
  234 + if (_start == null || _end == null) {
  235 + _start = _end = Offset.zero;
  236 + }
  237 + // Move the corresponding selection edge.
  238 + if (extendSelectionEvent.direction ==
  239 + SelectionExtendDirection.nextLine ||
  240 + horizontalBaseLine > size.width) {
  241 + newOffset = Offset.infinite;
  242 + } else {
  243 + newOffset = Offset.zero;
  244 + }
  245 + }
  246 + if (extendSelectionEvent.isEnd) {
  247 + if (newOffset == _end) {
  248 + result = forward ? SelectionResult.next : SelectionResult.previous;
  249 + }
  250 + _end = newOffset;
  251 + } else {
  252 + if (newOffset == _start) {
  253 + result = forward ? SelectionResult.next : SelectionResult.previous;
  254 + }
  255 + _start = newOffset;
  256 + }
  257 + }
  258 + _updateGeometry();
  259 + return result;
  260 + }
  261 +
  262 + // This method is called when users want to copy selected content in this
  263 + // widget into clipboard.
  264 + @override
  265 + SelectedContent? getSelectedContent() {
  266 + return value.hasSelection
  267 + ? SelectedContent(plainText: selectionText)
  268 + : null;
  269 + }
  270 +
  271 + LayerLink? _startHandle;
  272 + LayerLink? _endHandle;
  273 +
  274 + @override
  275 + void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle) {
  276 + if (_startHandle == startHandle && _endHandle == endHandle) {
  277 + return;
  278 + }
  279 + _startHandle = startHandle;
  280 + _endHandle = endHandle;
  281 + markNeedsPaint();
  282 + }
  283 +
  284 + @override
  285 + void paint(PaintingContext context, Offset offset) {
  286 + super.paint(context, offset);
  287 + if (!_geometry.value.hasSelection) {
  288 + return;
  289 + }
  290 + // Draw the selection highlight.
  291 + final Paint selectionPaint = Paint()
  292 + ..style = PaintingStyle.fill
  293 + ..color = _selectionColor;
  294 + context.canvas
  295 + .drawRect(_getSelectionHighlightRect().shift(offset), selectionPaint);
  296 +
  297 + // Push the layer links if any.
  298 + if (_startHandle != null) {
  299 + context.pushLayer(
  300 + LeaderLayer(
  301 + link: _startHandle!,
  302 + offset: offset + value.startSelectionPoint!.localPosition,
  303 + ),
  304 + (PaintingContext context, Offset offset) {},
  305 + Offset.zero,
  306 + );
  307 + }
  308 + if (_endHandle != null) {
  309 + context.pushLayer(
  310 + LeaderLayer(
  311 + link: _endHandle!,
  312 + offset: offset + value.endSelectionPoint!.localPosition,
  313 + ),
  314 + (PaintingContext context, Offset offset) {},
  315 + Offset.zero,
  316 + );
  317 + }
  318 + }
  319 +
  320 + @override
  321 + void dispose() {
  322 + _geometry.dispose();
  323 + super.dispose();
  324 + }
  325 +}
@@ -4,14 +4,15 @@ import 'package:flutter/material.dart'; @@ -4,14 +4,15 @@ import 'package:flutter/material.dart';
4 import 'package:gpt_markdown/custom_widgets/markdow_config.dart'; 4 import 'package:gpt_markdown/custom_widgets/markdow_config.dart';
5 5
6 import 'package:flutter/foundation.dart'; 6 import 'package:flutter/foundation.dart';
7 -import 'package:flutter/services.dart';  
8 import 'package:flutter_math_fork/flutter_math.dart'; 7 import 'package:flutter_math_fork/flutter_math.dart';
9 import 'package:gpt_markdown/custom_widgets/custom_divider.dart'; 8 import 'package:gpt_markdown/custom_widgets/custom_divider.dart';
10 import 'package:gpt_markdown/custom_widgets/custom_error_image.dart'; 9 import 'package:gpt_markdown/custom_widgets/custom_error_image.dart';
11 import 'package:gpt_markdown/custom_widgets/custom_rb_cb.dart'; 10 import 'package:gpt_markdown/custom_widgets/custom_rb_cb.dart';
  11 +import 'package:gpt_markdown/custom_widgets/selectable_adapter.dart';
12 import 'package:gpt_markdown/custom_widgets/unordered_ordered_list.dart'; 12 import 'package:gpt_markdown/custom_widgets/unordered_ordered_list.dart';
13 import 'dart:math'; 13 import 'dart:math';
14 14
  15 +import 'custom_widgets/code_field.dart';
15 import 'custom_widgets/link_button.dart'; 16 import 'custom_widgets/link_button.dart';
16 17
17 part 'theme.dart'; 18 part 'theme.dart';
@@ -19,8 +20,8 @@ part 'markdown_component.dart'; @@ -19,8 +20,8 @@ part 'markdown_component.dart';
19 part 'md_widget.dart'; 20 part 'md_widget.dart';
20 21
21 /// This widget create a full markdown widget as a column view. 22 /// This widget create a full markdown widget as a column view.
22 -class TexMarkdown extends StatelessWidget {  
23 - const TexMarkdown( 23 +class GptMarkdown extends StatelessWidget {
  24 + const GptMarkdown(
24 this.data, { 25 this.data, {
25 super.key, 26 super.key,
26 this.style, 27 this.style,
@@ -2,24 +2,24 @@ part of 'gpt_markdown.dart'; @@ -2,24 +2,24 @@ part of 'gpt_markdown.dart';
2 2
3 /// Markdown components 3 /// Markdown components
4 abstract class MarkdownComponent { 4 abstract class MarkdownComponent {
5 - static List<MarkdownComponent> components = [ 5 + static final List<MarkdownComponent> components = [
6 CodeBlockMd(), 6 CodeBlockMd(),
7 NewLines(), 7 NewLines(),
8 TableMd(), 8 TableMd(),
9 HTag(), 9 HTag(),
10 - IndentMd(),  
11 UnOrderedList(), 10 UnOrderedList(),
12 OrderedList(), 11 OrderedList(),
13 RadioButtonMd(), 12 RadioButtonMd(),
14 CheckBoxMd(), 13 CheckBoxMd(),
15 HrLine(), 14 HrLine(),
  15 + IndentMd(),
  16 + LatexMathMultyLine(),
  17 + LatexMath(),
16 ImageMd(), 18 ImageMd(),
17 HighlightedText(), 19 HighlightedText(),
18 StrikeMd(), 20 StrikeMd(),
19 BoldMd(), 21 BoldMd(),
20 ItalicMd(), 22 ItalicMd(),
21 - LatexMathMultyLine(),  
22 - LatexMath(),  
23 ATagMd(), 23 ATagMd(),
24 SourceTag(), 24 SourceTag(),
25 ]; 25 ];
@@ -38,22 +38,19 @@ abstract class MarkdownComponent { @@ -38,22 +38,19 @@ abstract class MarkdownComponent {
38 multiLine: true, 38 multiLine: true,
39 dotAll: true, 39 dotAll: true,
40 ); 40 );
41 - List<String> elements = [];  
42 text.splitMapJoin( 41 text.splitMapJoin(
43 combinedRegex, 42 combinedRegex,
44 onMatch: (p0) { 43 onMatch: (p0) {
45 String element = p0[0] ?? ""; 44 String element = p0[0] ?? "";
46 - elements.add(element);  
47 for (var each in components) { 45 for (var each in components) {
48 if (each.exp.hasMatch(element)) { 46 if (each.exp.hasMatch(element)) {
49 - if (each is InlineMd) { 47 + if (each.inline) {
50 spans.add(each.span( 48 spans.add(each.span(
51 context, 49 context,
52 element, 50 element,
53 config, 51 config,
54 )); 52 ));
55 } else { 53 } else {
56 - if (each is BlockMd) {  
57 spans.addAll([ 54 spans.addAll([
58 TextSpan( 55 TextSpan(
59 text: "\n ", 56 text: "\n ",
@@ -78,7 +75,6 @@ abstract class MarkdownComponent { @@ -78,7 +75,6 @@ abstract class MarkdownComponent {
78 ), 75 ),
79 ]); 76 ]);
80 } 77 }
81 - }  
82 return ""; 78 return "";
83 } 79 }
84 } 80 }
@@ -125,18 +121,38 @@ abstract class InlineMd extends MarkdownComponent { @@ -125,18 +121,38 @@ abstract class InlineMd extends MarkdownComponent {
125 abstract class BlockMd extends MarkdownComponent { 121 abstract class BlockMd extends MarkdownComponent {
126 @override 122 @override
127 bool get inline => false; 123 bool get inline => false;
  124 +
  125 + @override
  126 + RegExp get exp => RegExp(
  127 + r'^\ *?' + expString,
  128 + dotAll: true,
  129 + multiLine: true,
  130 + );
  131 +
  132 + String get expString;
  133 +
128 @override 134 @override
129 InlineSpan span( 135 InlineSpan span(
130 BuildContext context, 136 BuildContext context,
131 String text, 137 String text,
132 final GptMarkdownConfig config, 138 final GptMarkdownConfig config,
133 ) { 139 ) {
134 - return WidgetSpan(  
135 - child: build( 140 + var matches = RegExp(r'^(?<spaces>\ \ +).*').firstMatch(text);
  141 + var spaces = matches?.namedGroup('spaces');
  142 + var length = spaces?.length ?? 0;
  143 + var child = build(
136 context, 144 context,
137 text, 145 text,
138 config, 146 config,
139 - ), 147 + );
  148 + if (length > 0) {
  149 + child = UnorderedListView(
  150 + spacing: length.toDouble() * 6,
  151 + child: child,
  152 + );
  153 + }
  154 + return WidgetSpan(
  155 + child: child,
140 alignment: PlaceholderAlignment.middle, 156 alignment: PlaceholderAlignment.middle,
141 ); 157 );
142 } 158 }
@@ -151,57 +167,44 @@ abstract class BlockMd extends MarkdownComponent { @@ -151,57 +167,44 @@ abstract class BlockMd extends MarkdownComponent {
151 /// Heading component 167 /// Heading component
152 class HTag extends BlockMd { 168 class HTag extends BlockMd {
153 @override 169 @override
154 - RegExp get exp => RegExp(r"^(#{1,6})\ ([^\n]+?)$"); 170 + String get expString => (r"(?<hash>#{1,6})\ (?<data>[^\n]+?)$");
155 @override 171 @override
156 Widget build( 172 Widget build(
157 BuildContext context, 173 BuildContext context,
158 String text, 174 String text,
159 final GptMarkdownConfig config, 175 final GptMarkdownConfig config,
160 ) { 176 ) {
161 - var match = exp.firstMatch(text.trim()); 177 + var theme = GptMarkdownTheme.of(context);
  178 + var match = this.exp.firstMatch(text.trim());
162 var conf = config.copyWith( 179 var conf = config.copyWith(
163 style: [ 180 style: [
164 - Theme.of(context)  
165 - .textTheme  
166 - .headlineLarge  
167 - ?.copyWith(color: config.style?.color),  
168 - Theme.of(context)  
169 - .textTheme  
170 - .headlineMedium  
171 - ?.copyWith(color: config.style?.color),  
172 - Theme.of(context)  
173 - .textTheme  
174 - .headlineSmall  
175 - ?.copyWith(color: config.style?.color),  
176 - Theme.of(context)  
177 - .textTheme  
178 - .titleLarge  
179 - ?.copyWith(color: config.style?.color),  
180 - Theme.of(context)  
181 - .textTheme  
182 - .titleMedium  
183 - ?.copyWith(color: config.style?.color),  
184 - Theme.of(context)  
185 - .textTheme  
186 - .titleSmall  
187 - ?.copyWith(color: config.style?.color),  
188 - ][match![1]!.length - 1]); 181 + theme.h1,
  182 + theme.h2,
  183 + theme.h3,
  184 + theme.h4,
  185 + theme.h5,
  186 + theme.h6,
  187 + ][match![1]!.length - 1]
  188 + ?.copyWith(
  189 + color: config.style?.color,
  190 + ),
  191 + );
189 return config.getRich( 192 return config.getRich(
190 TextSpan( 193 TextSpan(
191 children: [ 194 children: [
192 ...(MarkdownComponent.generate( 195 ...(MarkdownComponent.generate(
193 context, 196 context,
194 - "${match[2]}", 197 + "${match.namedGroup('data')}",
195 conf, 198 conf,
196 )), 199 )),
197 - if (match[1]!.length == 1) ...[ 200 + if (match.namedGroup('hash')!.length == 1) ...[
198 const TextSpan( 201 const TextSpan(
199 text: "\n ", 202 text: "\n ",
200 style: TextStyle(fontSize: 0, height: 0), 203 style: TextStyle(fontSize: 0, height: 0),
201 ), 204 ),
202 WidgetSpan( 205 WidgetSpan(
203 child: CustomDivider( 206 child: CustomDivider(
204 - height: 2, 207 + height: theme.hrLineThickness,
205 color: config.style?.color ?? 208 color: config.style?.color ??
206 Theme.of(context).colorScheme.outline, 209 Theme.of(context).colorScheme.outline,
207 ), 210 ),
@@ -235,16 +238,18 @@ class NewLines extends InlineMd { @@ -235,16 +238,18 @@ class NewLines extends InlineMd {
235 /// Horizontal line component 238 /// Horizontal line component
236 class HrLine extends BlockMd { 239 class HrLine extends BlockMd {
237 @override 240 @override
238 - RegExp get exp => RegExp(r"^(--)[-]+$"); 241 + String get expString => (r"(--)[-]+$");
239 @override 242 @override
240 Widget build( 243 Widget build(
241 BuildContext context, 244 BuildContext context,
242 String text, 245 String text,
243 final GptMarkdownConfig config, 246 final GptMarkdownConfig config,
244 ) { 247 ) {
  248 + var thickness = GptMarkdownTheme.of(context).hrLineThickness;
  249 + var color = GptMarkdownTheme.of(context).hrLineColor;
245 return CustomDivider( 250 return CustomDivider(
246 - height: 2,  
247 - color: config.style?.color ?? Theme.of(context).colorScheme.outline, 251 + height: thickness,
  252 + color: config.style?.color ?? color,
248 ); 253 );
249 } 254 }
250 } 255 }
@@ -252,7 +257,7 @@ class HrLine extends BlockMd { @@ -252,7 +257,7 @@ class HrLine extends BlockMd {
252 /// Checkbox component 257 /// Checkbox component
253 class CheckBoxMd extends BlockMd { 258 class CheckBoxMd extends BlockMd {
254 @override 259 @override
255 - RegExp get exp => RegExp(r"^\[(\x?)\]\ (\S[^\n]*?)$"); 260 + String get expString => (r"\[(\x?)\]\ (\S[^\n]*?)$");
256 get onLinkTab => null; 261 get onLinkTab => null;
257 262
258 @override 263 @override
@@ -261,7 +266,7 @@ class CheckBoxMd extends BlockMd { @@ -261,7 +266,7 @@ class CheckBoxMd extends BlockMd {
261 String text, 266 String text,
262 final GptMarkdownConfig config, 267 final GptMarkdownConfig config,
263 ) { 268 ) {
264 - var match = exp.firstMatch(text.trim()); 269 + var match = this.exp.firstMatch(text.trim());
265 return CustomCb( 270 return CustomCb(
266 value: ("${match?[1]}" == "x"), 271 value: ("${match?[1]}" == "x"),
267 textDirection: config.textDirection, 272 textDirection: config.textDirection,
@@ -276,7 +281,7 @@ class CheckBoxMd extends BlockMd { @@ -276,7 +281,7 @@ class CheckBoxMd extends BlockMd {
276 /// Radio Button component 281 /// Radio Button component
277 class RadioButtonMd extends BlockMd { 282 class RadioButtonMd extends BlockMd {
278 @override 283 @override
279 - RegExp get exp => RegExp(r"^\((\x?)\)\ (\S[^\n]*)$"); 284 + String get expString => (r"\((\x?)\)\ (\S[^\n]*)$");
280 get onLinkTab => null; 285 get onLinkTab => null;
281 286
282 @override 287 @override
@@ -285,7 +290,7 @@ class RadioButtonMd extends BlockMd { @@ -285,7 +290,7 @@ class RadioButtonMd extends BlockMd {
285 String text, 290 String text,
286 final GptMarkdownConfig config, 291 final GptMarkdownConfig config,
287 ) { 292 ) {
288 - var match = exp.firstMatch(text.trim()); 293 + var match = this.exp.firstMatch(text.trim());
289 return CustomRb( 294 return CustomRb(
290 value: ("${match?[1]}" == "x"), 295 value: ("${match?[1]}" == "x"),
291 textDirection: config.textDirection, 296 textDirection: config.textDirection,
@@ -298,36 +303,45 @@ class RadioButtonMd extends BlockMd { @@ -298,36 +303,45 @@ class RadioButtonMd extends BlockMd {
298 } 303 }
299 304
300 /// Indent 305 /// Indent
301 -class IndentMd extends BlockMd { 306 +class IndentMd extends InlineMd {
302 @override 307 @override
303 - RegExp get exp => RegExp(r"^(\ \ \ \ +)([^\n]+)$");  
304 - get onLinkTab => null; 308 + RegExp get exp =>
  309 + RegExp(r"^(\ +)(((?!\n\n).)+)$", dotAll: true, multiLine: true);
305 310
306 @override 311 @override
307 - Widget build( 312 + InlineSpan span(
308 BuildContext context, 313 BuildContext context,
309 String text, 314 String text,
310 final GptMarkdownConfig config, 315 final GptMarkdownConfig config,
311 ) { 316 ) {
312 - [  
313 - r"\\\[(.*?)\\\]",  
314 - r"\\\((.*?)\\\)",  
315 - r"(?<!\\)\$((?:\\.|[^$])*?)\$(?!\\)",  
316 - ].join("|");  
317 var match = exp.firstMatch(text); 317 var match = exp.firstMatch(text);
318 int spaces = (match?[1] ?? "").length; 318 int spaces = (match?[1] ?? "").length;
319 - return UnorderedListView( 319 + var data = "${match?[2]}".trim();
  320 + data.replaceAll(RegExp(r'\n\ *'), '\n').trim();
  321 + var child = TextSpan(
  322 + children: MarkdownComponent.generate(
  323 + context,
  324 + data,
  325 + config,
  326 + ),
  327 + );
  328 + if (spaces < 4) {
  329 + return child;
  330 + }
  331 + return TextSpan(
  332 + children: [
  333 + const TextSpan(text: '\n'),
  334 + WidgetSpan(
  335 + child: UnorderedListView(
320 bulletColor: config.style?.color, 336 bulletColor: config.style?.color,
321 padding: spaces * 5, 337 padding: spaces * 5,
322 bulletSize: 0, 338 bulletSize: 0,
323 textDirection: config.textDirection, 339 textDirection: config.textDirection,
324 - child: Text.rich(TextSpan(  
325 - children: MarkdownComponent.generate(  
326 - context,  
327 - "${match?[2]}",  
328 - config, 340 + child: Text.rich(child),
329 ), 341 ),
330 - )), 342 + ),
  343 + const TextSpan(text: '\n'),
  344 + ],
331 ); 345 );
332 } 346 }
333 } 347 }
@@ -335,7 +349,7 @@ class IndentMd extends BlockMd { @@ -335,7 +349,7 @@ class IndentMd extends BlockMd {
335 /// Unordered list component 349 /// Unordered list component
336 class UnOrderedList extends BlockMd { 350 class UnOrderedList extends BlockMd {
337 @override 351 @override
338 - RegExp get exp => RegExp(r"^(?:\-|\*)\ ([^\n]+)$"); 352 + String get expString => (r"(?:\-|\*)\ ([^\n]+)$");
339 get onLinkTab => null; 353 get onLinkTab => null;
340 354
341 @override 355 @override
@@ -344,7 +358,7 @@ class UnOrderedList extends BlockMd { @@ -344,7 +358,7 @@ class UnOrderedList extends BlockMd {
344 String text, 358 String text,
345 final GptMarkdownConfig config, 359 final GptMarkdownConfig config,
346 ) { 360 ) {
347 - var match = exp.firstMatch(text); 361 + var match = this.exp.firstMatch(text);
348 return UnorderedListView( 362 return UnorderedListView(
349 bulletColor: 363 bulletColor:
350 config.style?.color ?? DefaultTextStyle.of(context).style.color, 364 config.style?.color ?? DefaultTextStyle.of(context).style.color,
@@ -365,7 +379,7 @@ class UnOrderedList extends BlockMd { @@ -365,7 +379,7 @@ class UnOrderedList extends BlockMd {
365 /// Ordered list component 379 /// Ordered list component
366 class OrderedList extends BlockMd { 380 class OrderedList extends BlockMd {
367 @override 381 @override
368 - RegExp get exp => RegExp(r"^([0-9]+\.)\ ([^\n]+)$"); 382 + String get expString => (r"([0-9]+\.)\ ([^\n]+)$");
369 383
370 get onLinkTab => null; 384 get onLinkTab => null;
371 385
@@ -375,7 +389,7 @@ class OrderedList extends BlockMd { @@ -375,7 +389,7 @@ class OrderedList extends BlockMd {
375 String text, 389 String text,
376 final GptMarkdownConfig config, 390 final GptMarkdownConfig config,
377 ) { 391 ) {
378 - var match = exp.firstMatch(text.trim()); 392 + var match = this.exp.firstMatch(text.trim());
379 return OrderedListView( 393 return OrderedListView(
380 no: "${match?[1]}", 394 no: "${match?[1]}",
381 textDirection: config.textDirection, 395 textDirection: config.textDirection,
@@ -508,10 +522,9 @@ class ItalicMd extends InlineMd { @@ -508,10 +522,9 @@ class ItalicMd extends InlineMd {
508 522
509 class LatexMathMultyLine extends BlockMd { 523 class LatexMathMultyLine extends BlockMd {
510 @override 524 @override
511 - RegExp get exp => RegExp(  
512 - r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})",  
513 - dotAll: true,  
514 - ); 525 + String get expString => (r"\\\[(((?!\n\n).)*)\\\]|(\\begin.*?\\end{.*?})");
  526 + @override
  527 + RegExp get exp => RegExp(expString, dotAll: true, multiLine: true);
515 528
516 @override 529 @override
517 Widget build( 530 Widget build(
@@ -520,13 +533,14 @@ class LatexMathMultyLine extends BlockMd { @@ -520,13 +533,14 @@ class LatexMathMultyLine extends BlockMd {
520 final GptMarkdownConfig config, 533 final GptMarkdownConfig config,
521 ) { 534 ) {
522 var p0 = exp.firstMatch(text.trim()); 535 var p0 = exp.firstMatch(text.trim());
523 - p0?.group(0);  
524 String mathText = p0?[1] ?? p0?[2] ?? ""; 536 String mathText = p0?[1] ?? p0?[2] ?? "";
525 var workaround = config.latexWorkaround ?? (String tex) => tex; 537 var workaround = config.latexWorkaround ?? (String tex) => tex;
526 538
527 var builder = config.latexBuilder ?? 539 var builder = config.latexBuilder ??
528 (BuildContext context, String tex, TextStyle textStyle, bool inline) => 540 (BuildContext context, String tex, TextStyle textStyle, bool inline) =>
529 - Math.tex( 541 + SelectableAdapter(
  542 + selectedText: tex,
  543 + child: Math.tex(
530 tex, 544 tex,
531 textStyle: textStyle, 545 textStyle: textStyle,
532 mathStyle: MathStyle.display, 546 mathStyle: MathStyle.display,
@@ -562,6 +576,7 @@ class LatexMathMultyLine extends BlockMd { @@ -562,6 +576,7 @@ class LatexMathMultyLine extends BlockMd {
562 : Theme.of(context).colorScheme.error), 576 : Theme.of(context).colorScheme.error),
563 ); 577 );
564 }, 578 },
  579 + ),
565 ); 580 );
566 return builder(context, workaround(mathText), 581 return builder(context, workaround(mathText),
567 config.style ?? const TextStyle(), false); 582 config.style ?? const TextStyle(), false);
@@ -591,7 +606,9 @@ class LatexMath extends InlineMd { @@ -591,7 +606,9 @@ class LatexMath extends InlineMd {
591 var workaround = config.latexWorkaround ?? (String tex) => tex; 606 var workaround = config.latexWorkaround ?? (String tex) => tex;
592 var builder = config.latexBuilder ?? 607 var builder = config.latexBuilder ??
593 (BuildContext context, String tex, TextStyle textStyle, bool inline) => 608 (BuildContext context, String tex, TextStyle textStyle, bool inline) =>
594 - Math.tex( 609 + SelectableAdapter(
  610 + selectedText: tex,
  611 + child: Math.tex(
595 tex, 612 tex,
596 textStyle: textStyle, 613 textStyle: textStyle,
597 mathStyle: MathStyle.display, 614 mathStyle: MathStyle.display,
@@ -627,6 +644,7 @@ class LatexMath extends InlineMd { @@ -627,6 +644,7 @@ class LatexMath extends InlineMd {
627 : Theme.of(context).colorScheme.error), 644 : Theme.of(context).colorScheme.error),
628 ); 645 );
629 }, 646 },
  647 + ),
630 ); 648 );
631 return WidgetSpan( 649 return WidgetSpan(
632 alignment: PlaceholderAlignment.baseline, 650 alignment: PlaceholderAlignment.baseline,
@@ -655,7 +673,6 @@ class SourceTag extends InlineMd { @@ -655,7 +673,6 @@ class SourceTag extends InlineMd {
655 } 673 }
656 return WidgetSpan( 674 return WidgetSpan(
657 alignment: PlaceholderAlignment.middle, 675 alignment: PlaceholderAlignment.middle,
658 - // baseline: TextBaseline.alphabetic,  
659 child: Padding( 676 child: Padding(
660 padding: const EdgeInsets.all(2), 677 padding: const EdgeInsets.all(2),
661 child: config.sourceTagBuilder 678 child: config.sourceTagBuilder
@@ -696,8 +713,11 @@ class ATagMd extends InlineMd { @@ -696,8 +713,11 @@ class ATagMd extends InlineMd {
696 if (match?[1] == null && match?[2] == null) { 713 if (match?[1] == null && match?[2] == null) {
697 return const TextSpan(); 714 return const TextSpan();
698 } 715 }
  716 + var theme = GptMarkdownTheme.of(context);
699 return WidgetSpan( 717 return WidgetSpan(
700 child: LinkButton( 718 child: LinkButton(
  719 + hoverColor: theme.linkHoverColor,
  720 + color: theme.linkColor,
701 onPressed: () { 721 onPressed: () {
702 config.onLinkTab?.call("${match?[2]}", "${match?[1]}"); 722 config.onLinkTab?.call("${match?[2]}", "${match?[1]}");
703 }, 723 },
@@ -762,6 +782,9 @@ class ImageMd extends InlineMd { @@ -762,6 +782,9 @@ class ImageMd extends InlineMd {
762 /// Table component 782 /// Table component
763 class TableMd extends BlockMd { 783 class TableMd extends BlockMd {
764 @override 784 @override
  785 + String get expString =>
  786 + (r"(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)$");
  787 + @override
765 Widget build( 788 Widget build(
766 BuildContext context, 789 BuildContext context,
767 String text, 790 String text,
@@ -847,28 +870,19 @@ class TableMd extends BlockMd { @@ -847,28 +870,19 @@ class TableMd extends BlockMd {
847 ), 870 ),
848 ); 871 );
849 } 872 }
850 -  
851 - @override  
852 - RegExp get exp => RegExp(  
853 - r"^(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)$",  
854 - );  
855 } 873 }
856 874
857 class CodeBlockMd extends BlockMd { 875 class CodeBlockMd extends BlockMd {
858 @override 876 @override
859 - RegExp get exp => RegExp(  
860 - r"\s*?```(.*?)\n((.*?)(:?\n\s*?```)|(.*)(:?\n```)?)$",  
861 - multiLine: true,  
862 - dotAll: true,  
863 - ); 877 + String get expString => r"```(.*?)\n((.*?)(:?\n\s*?```)|(.*)(:?\n```)?)$";
864 @override 878 @override
865 Widget build( 879 Widget build(
866 BuildContext context, 880 BuildContext context,
867 String text, 881 String text,
868 final GptMarkdownConfig config, 882 final GptMarkdownConfig config,
869 ) { 883 ) {
870 - String codes = exp.firstMatch(text)?[2] ?? "";  
871 - String name = exp.firstMatch(text)?[1] ?? ""; 884 + String codes = this.exp.firstMatch(text)?[2] ?? "";
  885 + String name = this.exp.firstMatch(text)?[1] ?? "";
872 codes = codes.replaceAll(r"```", "").trim(); 886 codes = codes.replaceAll(r"```", "").trim();
873 return Padding( 887 return Padding(
874 padding: const EdgeInsets.all(16.0), 888 padding: const EdgeInsets.all(16.0),
@@ -878,75 +892,3 @@ class CodeBlockMd extends BlockMd { @@ -878,75 +892,3 @@ class CodeBlockMd extends BlockMd {
878 ); 892 );
879 } 893 }
880 } 894 }
881 -  
882 -class CodeField extends StatefulWidget {  
883 - const CodeField({super.key, required this.name, required this.codes});  
884 - final String name;  
885 - final String codes;  
886 -  
887 - @override  
888 - State<CodeField> createState() => _CodeFieldState();  
889 -}  
890 -  
891 -class _CodeFieldState extends State<CodeField> {  
892 - bool _copied = false;  
893 - @override  
894 - Widget build(BuildContext context) {  
895 - return Material(  
896 - color: Theme.of(context).colorScheme.onInverseSurface,  
897 - shape: RoundedRectangleBorder(  
898 - borderRadius: BorderRadius.circular(8),  
899 - ),  
900 - child: Column(  
901 - crossAxisAlignment: CrossAxisAlignment.stretch,  
902 - children: [  
903 - Row(  
904 - children: [  
905 - Padding(  
906 - padding:  
907 - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),  
908 - child: Text(widget.name),  
909 - ),  
910 - const Spacer(),  
911 - TextButton.icon(  
912 - style: TextButton.styleFrom(  
913 - foregroundColor: Theme.of(context).colorScheme.onSurface,  
914 - textStyle: const TextStyle(  
915 - fontWeight: FontWeight.normal,  
916 - ),  
917 - ),  
918 - onPressed: () async {  
919 - await Clipboard.setData(ClipboardData(text: widget.codes))  
920 - .then((value) {  
921 - setState(() {  
922 - _copied = true;  
923 - });  
924 - });  
925 - await Future.delayed(const Duration(seconds: 2));  
926 - setState(() {  
927 - _copied = false;  
928 - });  
929 - },  
930 - icon: Icon(  
931 - (_copied) ? Icons.done : Icons.content_paste,  
932 - size: 15,  
933 - ),  
934 - label: Text((_copied) ? "Copied!" : "Copy code"),  
935 - ),  
936 - ],  
937 - ),  
938 - const Divider(  
939 - height: 1,  
940 - ),  
941 - SingleChildScrollView(  
942 - scrollDirection: Axis.horizontal,  
943 - padding: const EdgeInsets.all(16),  
944 - child: Text(  
945 - widget.codes,  
946 - ),  
947 - ),  
948 - ],  
949 - ),  
950 - );  
951 - }  
952 -}  
@@ -16,16 +16,17 @@ class MdWidget extends StatelessWidget { @@ -16,16 +16,17 @@ class MdWidget extends StatelessWidget {
16 list.addAll( 16 list.addAll(
17 MarkdownComponent.generate( 17 MarkdownComponent.generate(
18 context, 18 context,
19 - exp.replaceAllMapped(  
20 - RegExp(  
21 - r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})",  
22 - multiLine: true,  
23 - dotAll: true,  
24 - ), (match) {  
25 - //  
26 - String body = (match[1] ?? match[2])?.replaceAll("\n", " ") ?? "";  
27 - return "\\[$body\\]";  
28 - }), 19 + exp,
  20 + // .replaceAllMapped(
  21 + // RegExp(
  22 + // r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})",
  23 + // multiLine: true,
  24 + // dotAll: true,
  25 + // ), (match) {
  26 + // //
  27 + // String body = (match[1] ?? match[2])?.replaceAll("\n", " ") ?? "";
  28 + // return "\\[$body\\]";
  29 + // }),
29 config, 30 config,
30 ), 31 ),
31 ); 32 );
1 part of 'gpt_markdown.dart'; 1 part of 'gpt_markdown.dart';
2 2
3 -/// Theme defined for `TexMarkdown` widget 3 +/// Theme defined for `GptMarkdown` widget
4 class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> { 4 class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> {
5 - GptMarkdownThemeData({ 5 + GptMarkdownThemeData._({
6 required this.highlightColor, 6 required this.highlightColor,
  7 + required this.h1,
  8 + required this.h2,
  9 + required this.h3,
  10 + required this.h4,
  11 + required this.h5,
  12 + required this.h6,
  13 + required this.hrLineThickness,
  14 + required this.hrLineColor,
  15 + required this.linkColor,
  16 + required this.linkHoverColor,
7 }); 17 });
8 18
9 - /// Define default attributes.  
10 - factory GptMarkdownThemeData.from(BuildContext context) {  
11 - return GptMarkdownThemeData(  
12 - highlightColor:  
13 - Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(50), 19 + factory GptMarkdownThemeData({
  20 + required Brightness brightness,
  21 + Color? highlightColor,
  22 + TextStyle? h1,
  23 + TextStyle? h2,
  24 + TextStyle? h3,
  25 + TextStyle? h4,
  26 + TextStyle? h5,
  27 + TextStyle? h6,
  28 + double? hrLineThickness,
  29 + Color? hrLineColor,
  30 + Color? linkColor,
  31 + Color? linkHoverColor,
  32 + }) {
  33 + ThemeData themeData = switch (brightness) {
  34 + Brightness.light => ThemeData.light(),
  35 + Brightness.dark => ThemeData.dark(),
  36 + };
  37 + final typography = Typography.tall2021.copyWith(
  38 + displayLarge: Typography.tall2021.displayLarge?.copyWith(inherit: true),
  39 + displayMedium: Typography.tall2021.displayMedium?.copyWith(inherit: true),
  40 + displaySmall: Typography.tall2021.displaySmall?.copyWith(inherit: true),
  41 + headlineLarge: Typography.tall2021.headlineLarge?.copyWith(inherit: true),
  42 + headlineMedium:
  43 + Typography.tall2021.headlineMedium?.copyWith(inherit: true),
  44 + headlineSmall: Typography.tall2021.headlineSmall?.copyWith(inherit: true),
  45 + titleLarge: Typography.tall2021.titleLarge?.copyWith(inherit: true),
  46 + titleMedium: Typography.tall2021.titleMedium?.copyWith(inherit: true),
  47 + titleSmall: Typography.tall2021.titleSmall?.copyWith(inherit: true),
  48 + bodyLarge: Typography.tall2021.bodyLarge?.copyWith(inherit: true),
  49 + bodyMedium: Typography.tall2021.bodyMedium?.copyWith(inherit: true),
  50 + bodySmall: Typography.tall2021.bodySmall?.copyWith(inherit: true),
  51 + labelLarge: Typography.tall2021.labelLarge?.copyWith(inherit: true),
  52 + labelMedium: Typography.tall2021.labelMedium?.copyWith(inherit: true),
  53 + labelSmall: Typography.tall2021.labelSmall?.copyWith(inherit: true),
  54 + );
  55 + themeData = themeData.copyWith(
  56 + textTheme: typography,
  57 + );
  58 + TextTheme textTheme = themeData.textTheme;
  59 + return GptMarkdownThemeData._fromTheme(themeData, textTheme).copyWith(
  60 + highlightColor: highlightColor,
  61 + h1: h1,
  62 + h2: h2,
  63 + h3: h3,
  64 + h4: h4,
  65 + h5: h5,
  66 + h6: h6,
  67 + hrLineThickness: hrLineThickness,
  68 + hrLineColor: hrLineColor,
  69 + linkColor: linkColor,
  70 + linkHoverColor: linkHoverColor,
  71 + );
  72 + }
  73 +
  74 + factory GptMarkdownThemeData._fromTheme(
  75 + ThemeData theme, TextTheme textTheme) {
  76 + return GptMarkdownThemeData._(
  77 + highlightColor: theme.colorScheme.onSurfaceVariant.withAlpha(50),
  78 + h1: textTheme.headlineLarge,
  79 + h2: textTheme.headlineMedium,
  80 + h3: textTheme.headlineSmall,
  81 + h4: textTheme.titleLarge,
  82 + h5: textTheme.titleMedium,
  83 + h6: textTheme.titleSmall,
  84 + hrLineThickness: 1,
  85 + hrLineColor: theme.colorScheme.outline,
  86 + linkColor: Colors.blue,
  87 + linkHoverColor: Colors.red,
14 ); 88 );
15 } 89 }
16 90
17 Color highlightColor; 91 Color highlightColor;
  92 + TextStyle? h1;
  93 + TextStyle? h2;
  94 + TextStyle? h3;
  95 + TextStyle? h4;
  96 + TextStyle? h5;
  97 + TextStyle? h6;
  98 + double hrLineThickness;
  99 + Color hrLineColor;
  100 + Color linkColor;
  101 + Color linkHoverColor;
18 102
  103 + /// Define default attributes.
19 @override 104 @override
20 - GptMarkdownThemeData copyWith({Color? highlightColor}) {  
21 - return GptMarkdownThemeData( 105 + GptMarkdownThemeData copyWith({
  106 + Color? highlightColor,
  107 + TextStyle? h1,
  108 + TextStyle? h2,
  109 + TextStyle? h3,
  110 + TextStyle? h4,
  111 + TextStyle? h5,
  112 + TextStyle? h6,
  113 + double? hrLineThickness,
  114 + Color? hrLineColor,
  115 + Color? linkColor,
  116 + Color? linkHoverColor,
  117 + }) {
  118 + return GptMarkdownThemeData._(
22 highlightColor: highlightColor ?? this.highlightColor, 119 highlightColor: highlightColor ?? this.highlightColor,
  120 + h1: h1 ?? this.h1,
  121 + h2: h2 ?? this.h2,
  122 + h3: h3 ?? this.h3,
  123 + h4: h4 ?? this.h4,
  124 + h5: h5 ?? this.h5,
  125 + h6: h6 ?? this.h6,
  126 + hrLineThickness: hrLineThickness ?? this.hrLineThickness,
  127 + hrLineColor: hrLineColor ?? this.hrLineColor,
  128 + linkColor: linkColor ?? this.linkColor,
  129 + linkHoverColor: linkHoverColor ?? this.linkHoverColor,
23 ); 130 );
24 } 131 }
25 132
@@ -28,9 +135,21 @@ class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> { @@ -28,9 +135,21 @@ class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> {
28 if (other == null) { 135 if (other == null) {
29 return this; 136 return this;
30 } 137 }
31 - return GptMarkdownThemeData( 138 + return GptMarkdownThemeData._(
32 highlightColor: 139 highlightColor:
33 Color.lerp(highlightColor, other.highlightColor, t) ?? highlightColor, 140 Color.lerp(highlightColor, other.highlightColor, t) ?? highlightColor,
  141 + h1: TextStyle.lerp(h1, other.h1, t) ?? h1,
  142 + h2: TextStyle.lerp(h2, other.h2, t) ?? h2,
  143 + h3: TextStyle.lerp(h3, other.h3, t) ?? h3,
  144 + h4: TextStyle.lerp(h4, other.h4, t) ?? h4,
  145 + h5: TextStyle.lerp(h5, other.h5, t) ?? h5,
  146 + h6: TextStyle.lerp(h6, other.h6, t) ?? h6,
  147 + hrLineThickness: Tween(begin: hrLineThickness, end: other.hrLineThickness)
  148 + .transform(t),
  149 + hrLineColor: Color.lerp(hrLineColor, other.hrLineColor, t) ?? hrLineColor,
  150 + linkColor: Color.lerp(linkColor, other.linkColor, t) ?? linkColor,
  151 + linkHoverColor:
  152 + Color.lerp(linkHoverColor, other.linkHoverColor, t) ?? linkHoverColor,
34 ); 153 );
35 } 154 }
36 } 155 }
@@ -45,16 +164,17 @@ class GptMarkdownTheme extends InheritedWidget { @@ -45,16 +164,17 @@ class GptMarkdownTheme extends InheritedWidget {
45 final GptMarkdownThemeData gptThemeData; 164 final GptMarkdownThemeData gptThemeData;
46 165
47 static GptMarkdownThemeData of(BuildContext context) { 166 static GptMarkdownThemeData of(BuildContext context) {
  167 + var theme = Theme.of(context);
48 final provider = 168 final provider =
49 context.dependOnInheritedWidgetOfExactType<GptMarkdownTheme>(); 169 context.dependOnInheritedWidgetOfExactType<GptMarkdownTheme>();
50 if (provider != null) { 170 if (provider != null) {
51 return provider.gptThemeData; 171 return provider.gptThemeData;
52 } 172 }
53 - final themeData = Theme.of(context).extension<GptMarkdownThemeData>(); 173 + final themeData = theme.extension<GptMarkdownThemeData>();
54 if (themeData != null) { 174 if (themeData != null) {
55 return themeData; 175 return themeData;
56 } 176 }
57 - return GptMarkdownThemeData.from(context); 177 + return GptMarkdownThemeData._fromTheme(theme, theme.textTheme);
58 } 178 }
59 179
60 @override 180 @override
1 name: gpt_markdown 1 name: gpt_markdown
2 -description: "The purpose of this package is to render the response of ChatGPT into a Flutter app."  
3 -version: 0.1.14 2 +description: "Powerful Markdown & LaTeX Renderer for Flutter: Rich Text, Math, Tables, Links, and Text Selection. Ideal for ChatGPT, Gemini, and more."
  3 +version: 1.0.0
4 homepage: https://github.com/Infinitix-LLC/gpt_markdown 4 homepage: https://github.com/Infinitix-LLC/gpt_markdown
5 5
6 environment: 6 environment:
@@ -10,46 +10,21 @@ environment: @@ -10,46 +10,21 @@ environment:
10 dependencies: 10 dependencies:
11 flutter: 11 flutter:
12 sdk: flutter 12 sdk: flutter
13 - flutter_math_fork: ^0.7.2 13 + flutter_math_fork: ^0.7.3
14 14
15 dev_dependencies: 15 dev_dependencies:
16 flutter_test: 16 flutter_test:
17 sdk: flutter 17 sdk: flutter
18 flutter_lints: ^4.0.0 18 flutter_lints: ^4.0.0
19 19
20 -# For information on the generic Dart part of this file, see the  
21 -# following page: https://dart.dev/tools/pub/pubspec  
22 -  
23 -# The following section is specific to Flutter packages.  
24 flutter: 20 flutter:
25 21
26 - # To add assets to your package, add an assets section, like this:  
27 - # assets:  
28 - # - images/a_dot_burr.jpeg  
29 - # - images/a_dot_ham.jpeg  
30 - #  
31 - # For details regarding assets in packages, see  
32 - # https://flutter.dev/assets-and-images/#from-packages  
33 - #  
34 - # An image asset can refer to one or more resolution-specific "variants", see  
35 - # https://flutter.dev/assets-and-images/#resolution-aware  
36 -  
37 - # To add custom fonts to your package, add a fonts section here,  
38 - # in this "flutter" section. Each entry in this list should have a  
39 - # "family" key with the font family name, and a "fonts" key with a  
40 - # list giving the asset and other descriptors for the font. For  
41 - # example:  
42 - # fonts:  
43 - # - family: Schyler  
44 - # fonts:  
45 - # - asset: fonts/Schyler-Regular.ttf  
46 - # - asset: fonts/Schyler-Italic.ttf  
47 - # style: italic  
48 - # - family: Trajan Pro  
49 - # fonts:  
50 - # - asset: fonts/TrajanPro.ttf  
51 - # - asset: fonts/TrajanPro_Bold.ttf  
52 - # weight: 700  
53 - #  
54 - # For details regarding fonts in packages, see  
55 - # https://flutter.dev/custom-fonts/#from-packages 22 +topics:
  23 + - markdown
  24 + - latex
  25 + - selectable
  26 + - math
  27 + - chatgpt
  28 + - gemini
  29 + - ai
  30 + - gpt