saminsohag

readme updated

1 # 📦 GPT Markdown & LaTeX for Flutter 1 # 📦 GPT Markdown & LaTeX for Flutter
2 2
3 -![Pub Version](https://img.shields.io/pub/v/gpt_markdown) 3 +[![Pub Version](https://img.shields.io/pub/v/gpt_markdown)](https://pub.dev/packages/gpt_markdown) [![Pub Likes](https://img.shields.io/pub/likes/gpt_markdown)](https://pub.dev/packages/gpt_markdown) [![Pub Points](https://img.shields.io/pub/points/gpt_markdown)](https://pub.dev/packages/gpt_markdown) [![GitHub](https://img.shields.io/badge/github-gpt__markdown-blue?logo=github)](https://github.com/Infinitix-LLC/gpt_markdown)
4 4
5 A comprehensive Flutter package for rendering rich Markdown and LaTeX content in your apps, designed for seamless integration with AI outputs like ChatGPT and Gemini. 5 A comprehensive Flutter package for rendering rich Markdown and LaTeX content in your apps, designed for seamless integration with AI outputs like ChatGPT and Gemini.
6 6
@@ -8,71 +8,106 @@ A comprehensive Flutter package for rendering rich Markdown and LaTeX content in @@ -8,71 +8,106 @@ A comprehensive Flutter package for rendering rich Markdown and LaTeX content in
8 8
9 --- 9 ---
10 10
  11 +## Supported Markdown Features
  12 +| ✨ Feature | ✅ Supported | 🔜 Upcoming |
  13 +| --- | --- | --- |
  14 +| 💻 Code Block | ✅ | |
  15 +| 📊 Table | ✅ | |
  16 +| 📝 Heading | ✅ | |
  17 +| 📌 Unordered List | ✅ | |
  18 +| 📋 Ordered List | ✅ | |
  19 +| 🔘 Radio Button | ✅ | |
  20 +| ☑️ Check Box | ✅ | |
  21 +| ➖ Horizontal Line | ✅ | |
  22 +| 🔢 Latex Math | ✅ | |
  23 +| ↩️ Indent | ✅ |
  24 +| 🖼️ Image | ✅ |
  25 +| ✨ Highlighted Text | ✅ |
  26 +| ✂️ Striked Text | ✅ |
  27 +| 🔵 Bold Text | ✅ |
  28 +| 📜 Italic Text | ✅ |
  29 +| 🔗 Links | ✅ |
  30 +| 📎 Underline | | 🔜 |
  31 +| 🧩 Custom components | | 🔜 |
  32 +
11 ## ✨ Key Features 33 ## ✨ Key Features
12 34
13 Render a wide variety of content with full Markdown and LaTeX support, including: 35 Render a wide variety of content with full Markdown and LaTeX support, including:
14 36
15 - List 37 - List
16 -  
17 - - Unordered list item  
18 - 1. Ordered list item 38 +```
  39 +- Unordered list item
  40 +1. Ordered list item
  41 +```
19 42
20 - Horizontal line 43 - Horizontal line
21 -  
22 - --- 44 +```
  45 +---
  46 +```
23 47
24 - Links 48 - Links
25 -  
26 - [<text here>](<href>) 49 +```
  50 +[<text here>](<href>)
  51 +```
27 52
28 - Images with size 53 - Images with size
29 -  
30 - ![<with>x<hight> someText](url) 54 +```
  55 +![<with>x<hight> someText](url)
  56 +```
31 - Table 57 - Table
32 58
33 - ```  
34 - | Name | Roll |  
35 - |-------------|-------------|  
36 - | sohag | 1 |  
37 -  
38 - ```  
39 - | Name | Roll |  
40 - |-------------|-------------|  
41 - | sohag | 1 |  
42 -  
43 -- Striked text 59 +```
  60 +| Name | Roll |
  61 +|-------|------|
  62 +| sohag | 1 |
44 63
45 - ~~striked text~~ 64 +```
46 65
47 -- Bold text 66 +| Name | Roll |
  67 +|-------|------|
  68 +| sohag | 1 |
48 69
49 - **Bold text** 70 +- ~~Striked text~~
  71 +```
  72 +~~striked text~~
  73 +```
50 74
51 -- Italic text 75 +- **Bold text**
  76 +```
  77 +**Bold text**
  78 +```
52 79
53 - *Italic text* 80 +- *Italic text*
  81 +```
  82 +*Italic text*
  83 +```
54 84
55 - heading texts 85 - heading texts
56 86
57 - # Heading 1  
58 - ## Heading 2  
59 - ### Heading 3  
60 - #### Heading 4  
61 - ##### Heading 5  
62 - ###### Heading 6 87 +```
  88 +# Heading 1
  89 +## Heading 2
  90 +### Heading 3
  91 +#### Heading 4
  92 +##### Heading 5
  93 +###### Heading 6
  94 +```
63 95
64 - Latex formula `\(\frac a b\)` or `\[\frac ab\]` 96 - Latex formula `\(\frac a b\)` or `\[\frac ab\]`
65 -  
66 - \(\frac a b\) 97 +```
  98 +\(\frac a b\)
  99 +```
67 100
68 - Radio button and checkbox 101 - Radio button and checkbox
69 102
70 - () Unchecked radio  
71 - (x) Checked radio  
72 - [] Unchecked checkbox  
73 - [x] Checked checkbox 103 +```
  104 +() Unchecked radio
  105 +(x) Checked radio
  106 +[] Unchecked checkbox
  107 +[x] Checked checkbox
  108 +```
74 109
75 -- You can also make the content selectable using `SelectiionArea` widget. 110 +- You can also make the content selectable using `SelectionArea` widget.
76 111
77 ## 🚀 Why Use GPT Markdown? 112 ## 🚀 Why Use GPT Markdown?
78 113
@@ -90,7 +125,7 @@ flutter pub add gpt_markdown @@ -90,7 +125,7 @@ flutter pub add gpt_markdown
90 125
91 ## 📖 Usage 126 ## 📖 Usage
92 127
93 -Check the documentation [here.](https://github.com/saminsohag/flutter_packages/tree/main/gpt_markdown/example) 128 +Check the documentation [here.](https://github.com/Infinitix-LLC/gpt_markdown/tree/main/example)
94 129
95 ```dart 130 ```dart
96 import 'package:flutter/material.dart'; 131 import 'package:flutter/material.dart';
@@ -145,6 +180,8 @@ You can also use LaTeX for mathematical expressions. Here's an example: @@ -145,6 +180,8 @@ You can also use LaTeX for mathematical expressions. Here's an example:
145 180
146 Markdown and LaTeX can be powerful tools for formatting text and mathematical expressions in your Flutter app. If you have any questions or need further assistance, feel free to ask! 181 Markdown and LaTeX can be powerful tools for formatting text and mathematical expressions in your Flutter app. If you have any questions or need further assistance, feel free to ask!
147 ``` 182 ```
  183 +### Output from gpt_markdown
  184 +
148 <img width="614" alt="Screenshot 2024-02-15 at 4 13 59 AM" src="https://github.com/saminsohag/flutter_packages/assets/59507062/8f4a4068-a12c-45d1-a954-ebaf3822e754"> 185 <img width="614" alt="Screenshot 2024-02-15 at 4 13 59 AM" src="https://github.com/saminsohag/flutter_packages/assets/59507062/8f4a4068-a12c-45d1-a954-ebaf3822e754">
149 186
150 187
@@ -73,3 +73,55 @@ class _MyHomePageState extends State<MyHomePage> { @@ -73,3 +73,55 @@ class _MyHomePageState extends State<MyHomePage> {
73 } 73 }
74 74
75 ``` 75 ```
  76 +
  77 +Use `SelectableAdapter` to make any non selectable widget selectable.
  78 +
  79 +```dart
  80 +SelectableAdapter(
  81 + selectedText: 'sin(x^2)',
  82 + child: Math.tex('sin(x^2)'),
  83 +);
  84 +```
  85 +
  86 +Use `GptMarkdownTheme` widget and `GptMarkdownThemeData` to customize the GptMarkdown.
  87 +
  88 +```dart
  89 +GptMarkdownTheme(
  90 + data: GptMarkdownThemeData.of(context).copyWith(
  91 + highlightColor: Colors.red,
  92 + ),
  93 + child: GptMarkdown(
  94 + text,
  95 + ),
  96 +);
  97 +```
  98 +
  99 +In theme extension you can use `GptMarkdownThemeData` to customize the GptMarkdown.
  100 +
  101 +```dart
  102 +theme: ThemeData(
  103 + useMaterial3: true,
  104 + brightness: Brightness.light,
  105 + colorSchemeSeed: Colors.blue,
  106 + extensions: [
  107 + GptMarkdownThemeData(
  108 + brightness: Brightness.light,
  109 + highlightColor: Colors.red,
  110 + ),
  111 + ],
  112 +),
  113 +darkTheme: ThemeData(
  114 + useMaterial3: true,
  115 + brightness: Brightness.dark,
  116 + colorSchemeSeed: Colors.blue,
  117 + extensions: [
  118 + GptMarkdownThemeData(
  119 + brightness: Brightness.dark,
  120 + highlightColor: Colors.red,
  121 + ),
  122 + ],
  123 +),
  124 +```
  125 +
  126 +Please see the [README.md](https://github.com/Infinitix-LLC/gpt_markdown) and also [example](https://github.com/Infinitix-LLC/gpt_markdown/tree/main/example/lib/main.dart) app for more details.
  127 +
@@ -2,10 +2,10 @@ import 'dart:io'; @@ -2,10 +2,10 @@ import 'dart:io';
2 2
3 import 'package:desktop_drop/desktop_drop.dart'; 3 import 'package:desktop_drop/desktop_drop.dart';
4 import 'package:flutter/material.dart'; 4 import 'package:flutter/material.dart';
  5 +import 'package:gpt_markdown/custom_widgets/selectable_adapter.dart';
5 import 'package:gpt_markdown/gpt_markdown.dart'; 6 import 'package:gpt_markdown/gpt_markdown.dart';
6 import 'package:flutter_math_fork/flutter_math.dart'; 7 import 'package:flutter_math_fork/flutter_math.dart';
7 import 'package:watcher/watcher.dart'; 8 import 'package:watcher/watcher.dart';
8 -import 'selectable_adapter.dart';  
9 9
10 void main() { 10 void main() {
11 runApp(const MyApp()); 11 runApp(const MyApp());
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 -}