saminsohag

theme extantions added

  1 +## 0.1.11
  2 +
  3 +* `GptMarkdownTheme` and `GptMarkdownThemeData` classes added.
  4 +
1 ## 0.1.10 5 ## 0.1.10
2 6
3 * components are now selectable. 7 * components are now selectable.
@@ -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 ),
  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: