Showing
8 changed files
with
424 additions
and
12 deletions
@@ -4,7 +4,9 @@ import 'package:desktop_drop/desktop_drop.dart'; | @@ -4,7 +4,9 @@ import 'package:desktop_drop/desktop_drop.dart'; | ||
4 | import 'package:flutter/material.dart'; | 4 | import 'package:flutter/material.dart'; |
5 | import 'package:gpt_markdown/gpt_markdown.dart'; | 5 | import 'package:gpt_markdown/gpt_markdown.dart'; |
6 | import 'package:flutter_math_fork/flutter_math.dart'; | 6 | import 'package:flutter_math_fork/flutter_math.dart'; |
7 | +import 'package:gpt_markdown/theme.dart'; | ||
7 | import 'package:watcher/watcher.dart'; | 8 | import 'package:watcher/watcher.dart'; |
9 | +import 'selectable_adapter.dart'; | ||
8 | 10 | ||
9 | void main() { | 11 | void main() { |
10 | runApp(const MyApp()); | 12 | runApp(const MyApp()); |
@@ -34,7 +36,11 @@ class _MyAppState extends State<MyApp> { | @@ -34,7 +36,11 @@ class _MyAppState extends State<MyApp> { | ||
34 | useMaterial3: true, | 36 | useMaterial3: true, |
35 | brightness: Brightness.dark, | 37 | brightness: Brightness.dark, |
36 | colorSchemeSeed: Colors.blue, | 38 | colorSchemeSeed: Colors.blue, |
39 | + extensions: [ | ||
40 | + GptMarkdownThemeData( | ||
41 | + highlightColor: Colors.red, | ||
37 | ), | 42 | ), |
43 | + ]), | ||
38 | home: MyHomePage( | 44 | home: MyHomePage( |
39 | title: 'GptMarkdown', | 45 | title: 'GptMarkdown', |
40 | onPressed: () { | 46 | onPressed: () { |
@@ -147,7 +153,11 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -147,7 +153,11 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
147 | 153 | ||
148 | @override | 154 | @override |
149 | Widget build(BuildContext context) { | 155 | Widget build(BuildContext context) { |
150 | - return Scaffold( | 156 | + return GptMarkdownTheme( |
157 | + gptThemeData: GptMarkdownTheme.of(context).copyWith( | ||
158 | + highlightColor: Colors.purple, | ||
159 | + ), | ||
160 | + child: Scaffold( | ||
151 | appBar: AppBar( | 161 | appBar: AppBar( |
152 | title: Text(widget.title), | 162 | title: Text(widget.title), |
153 | actions: [ | 163 | actions: [ |
@@ -180,8 +190,8 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -180,8 +190,8 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
180 | onPressed: () => setState(() { | 190 | onPressed: () => setState(() { |
181 | writingMod = !writingMod; | 191 | writingMod = !writingMod; |
182 | }), | 192 | }), |
183 | - icon: | ||
184 | - Icon(writingMod ? Icons.arrow_drop_down : Icons.arrow_drop_up), | 193 | + icon: Icon( |
194 | + writingMod ? Icons.arrow_drop_down : Icons.arrow_drop_up), | ||
185 | ), | 195 | ), |
186 | ], | 196 | ], |
187 | ), | 197 | ), |
@@ -211,7 +221,8 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -211,7 +221,8 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
211 | decoration: BoxDecoration( | 221 | decoration: BoxDecoration( |
212 | border: Border.all( | 222 | border: Border.all( |
213 | width: 1, | 223 | width: 1, |
214 | - color: Theme.of(context).colorScheme.outline), | 224 | + color: |
225 | + Theme.of(context).colorScheme.outline), | ||
215 | ), | 226 | ), |
216 | child: Theme( | 227 | child: Theme( |
217 | data: Theme.of(context), | 228 | data: Theme.of(context), |
@@ -324,6 +335,10 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -324,6 +335,10 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
324 | ), | 335 | ), |
325 | ); | 336 | ); |
326 | } | 337 | } |
338 | + child = SelectableAdapter( | ||
339 | + selectedText: tex, | ||
340 | + child: Math.tex(tex), | ||
341 | + ); | ||
327 | child = InkWell( | 342 | child = InkWell( |
328 | onTap: () { | 343 | onTap: () { |
329 | debugPrint("Hello world"); | 344 | debugPrint("Hello world"); |
@@ -346,13 +361,16 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -346,13 +361,16 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
346 | borderRadius: | 361 | borderRadius: |
347 | BorderRadius.circular(10), | 362 | BorderRadius.circular(10), |
348 | ), | 363 | ), |
349 | - child: Center(child: Text("$value")), | 364 | + child: |
365 | + Center(child: Text("$value")), | ||
350 | ), | 366 | ), |
351 | ); | 367 | ); |
352 | }, | 368 | }, |
353 | ); | 369 | ); |
354 | if (selectable) { | 370 | if (selectable) { |
355 | - child = SelectionArea(child: child); | 371 | + child = SelectionArea( |
372 | + child: child, | ||
373 | + ); | ||
356 | } | 374 | } |
357 | return child; | 375 | return child; |
358 | }, | 376 | }, |
@@ -384,6 +402,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -384,6 +402,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
384 | ], | 402 | ], |
385 | ), | 403 | ), |
386 | ), | 404 | ), |
405 | + ), | ||
387 | ); | 406 | ); |
388 | } | 407 | } |
389 | } | 408 | } |
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 | +} |
@@ -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.10" | 129 | + version: "0.1.11" |
130 | http: | 130 | http: |
131 | dependency: transitive | 131 | dependency: transitive |
132 | description: | 132 | description: |
@@ -373,5 +373,5 @@ packages: | @@ -373,5 +373,5 @@ packages: | ||
373 | source: hosted | 373 | source: hosted |
374 | version: "6.5.0" | 374 | version: "6.5.0" |
375 | sdks: | 375 | sdks: |
376 | - dart: ">=3.3.0 <4.0.0" | 376 | + dart: ">=3.5.0 <4.0.0" |
377 | flutter: ">=3.18.0-18.0.pre.54" | 377 | flutter: ">=3.18.0-18.0.pre.54" |
@@ -5,7 +5,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev | @@ -5,7 +5,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev | ||
5 | version: 1.0.0+1 | 5 | version: 1.0.0+1 |
6 | 6 | ||
7 | environment: | 7 | environment: |
8 | - sdk: '>=2.18.6 <4.0.0' | 8 | + sdk: '>=3.5.0 <4.0.0' |
9 | dependencies: | 9 | dependencies: |
10 | flutter: | 10 | flutter: |
11 | sdk: flutter | 11 | sdk: flutter |
@@ -7,6 +7,7 @@ import 'package:gpt_markdown/custom_widgets/custom_error_image.dart'; | @@ -7,6 +7,7 @@ import 'package:gpt_markdown/custom_widgets/custom_error_image.dart'; | ||
7 | import 'package:gpt_markdown/custom_widgets/custom_rb_cb.dart'; | 7 | import 'package:gpt_markdown/custom_widgets/custom_rb_cb.dart'; |
8 | import 'package:gpt_markdown/custom_widgets/markdow_config.dart'; | 8 | import 'package:gpt_markdown/custom_widgets/markdow_config.dart'; |
9 | import 'package:gpt_markdown/custom_widgets/unordered_ordered_list.dart'; | 9 | import 'package:gpt_markdown/custom_widgets/unordered_ordered_list.dart'; |
10 | +import 'package:gpt_markdown/theme.dart'; | ||
10 | import 'md_widget.dart'; | 11 | import 'md_widget.dart'; |
11 | 12 | ||
12 | /// Markdown components | 13 | /// Markdown components |
@@ -413,14 +414,14 @@ class HighlightedText extends InlineMd { | @@ -413,14 +414,14 @@ class HighlightedText extends InlineMd { | ||
413 | style: config.style?.copyWith( | 414 | style: config.style?.copyWith( |
414 | fontWeight: FontWeight.bold, | 415 | fontWeight: FontWeight.bold, |
415 | background: Paint() | 416 | background: Paint() |
416 | - ..color = Theme.of(context).colorScheme.onInverseSurface | 417 | + ..color = GptMarkdownTheme.of(context).highlightColor |
417 | ..strokeCap = StrokeCap.round | 418 | ..strokeCap = StrokeCap.round |
418 | ..strokeJoin = StrokeJoin.round, | 419 | ..strokeJoin = StrokeJoin.round, |
419 | ) ?? | 420 | ) ?? |
420 | TextStyle( | 421 | TextStyle( |
421 | fontWeight: FontWeight.bold, | 422 | fontWeight: FontWeight.bold, |
422 | background: Paint() | 423 | background: Paint() |
423 | - ..color = Theme.of(context).colorScheme.surfaceContainerHighest | 424 | + ..color = GptMarkdownTheme.of(context).highlightColor |
424 | ..strokeCap = StrokeCap.round | 425 | ..strokeCap = StrokeCap.round |
425 | ..strokeJoin = StrokeJoin.round, | 426 | ..strokeJoin = StrokeJoin.round, |
426 | ), | 427 | ), |
gpt_markdown/lib/theme.dart
0 → 100644
1 | +import 'package:flutter/material.dart'; | ||
2 | + | ||
3 | +/// Theme defined for `TexMarkdown` widget | ||
4 | +class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> { | ||
5 | + GptMarkdownThemeData({ | ||
6 | + required this.highlightColor, | ||
7 | + }); | ||
8 | + | ||
9 | + /// Define default attributes. | ||
10 | + factory GptMarkdownThemeData.from(BuildContext context) { | ||
11 | + return GptMarkdownThemeData( | ||
12 | + highlightColor: Theme.of(context).colorScheme.onInverseSurface, | ||
13 | + ); | ||
14 | + } | ||
15 | + | ||
16 | + Color highlightColor; | ||
17 | + | ||
18 | + @override | ||
19 | + GptMarkdownThemeData copyWith({Color? highlightColor}) { | ||
20 | + return GptMarkdownThemeData( | ||
21 | + highlightColor: highlightColor ?? this.highlightColor, | ||
22 | + ); | ||
23 | + } | ||
24 | + | ||
25 | + @override | ||
26 | + GptMarkdownThemeData lerp(GptMarkdownThemeData? other, double t) { | ||
27 | + if (other == null) { | ||
28 | + return this; | ||
29 | + } | ||
30 | + return GptMarkdownThemeData( | ||
31 | + highlightColor: | ||
32 | + Color.lerp(highlightColor, other.highlightColor, t) ?? highlightColor, | ||
33 | + ); | ||
34 | + } | ||
35 | +} | ||
36 | + | ||
37 | +/// Wrap a `Widget` with `GptMarkdownTheme` to provide `GptMarkdownThemeData` in your intiar app. | ||
38 | +class GptMarkdownTheme extends InheritedWidget { | ||
39 | + const GptMarkdownTheme({ | ||
40 | + super.key, | ||
41 | + required this.gptThemeData, | ||
42 | + required super.child, | ||
43 | + }); | ||
44 | + final GptMarkdownThemeData gptThemeData; | ||
45 | + | ||
46 | + static GptMarkdownThemeData of(BuildContext context) { | ||
47 | + final provider = | ||
48 | + context.dependOnInheritedWidgetOfExactType<GptMarkdownTheme>(); | ||
49 | + if (provider != null) { | ||
50 | + return provider.gptThemeData; | ||
51 | + } | ||
52 | + final themeData = Theme.of(context).extension<GptMarkdownThemeData>(); | ||
53 | + if (themeData != null) { | ||
54 | + return themeData; | ||
55 | + } | ||
56 | + return GptMarkdownThemeData.from(context); | ||
57 | + } | ||
58 | + | ||
59 | + @override | ||
60 | + bool updateShouldNotify(GptMarkdownTheme oldWidget) { | ||
61 | + return gptThemeData != oldWidget.gptThemeData; | ||
62 | + } | ||
63 | +} |
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." | 2 | description: "The purpose of this package is to render the response of ChatGPT into a Flutter app." |
3 | -version: 0.1.10 | 3 | +version: 0.1.11 |
4 | homepage: https://github.com/saminsohag/flutter_packages/tree/main/gpt_markdown | 4 | homepage: https://github.com/saminsohag/flutter_packages/tree/main/gpt_markdown |
5 | 5 | ||
6 | environment: | 6 | environment: |
-
Please register or login to post a comment