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