saminsohag

new release

## 1.0.0
* `TexMarkdown` is renamed to `GptMarkdown`.
* `h1` to `h6` style added to `GptMarkdownThemeData` class.
* `hrLineThickness` value added to `GptMarkdownThemeData` class.
* `hrLineColor` Color added to `GptMarkdownThemeData` class.
* `linkColor` Color added to `GptMarkdownThemeData` class.
* `linkHoverColor` Color added to `GptMarkdownThemeData` class.
* Indentation improved.
* Math equations are now default selectable.
* `SelectableAdapter` Widget added to make any widget selectable.
## 0.1.15
* `CodeBlock` is moved out of `gpt_markdown.dart` library.
## 0.1.14
* Cnaged `withOpacity` to `withAlpha` in `theme.dart` for highlightColor.
* Changed `withOpacity` to `withAlpha` in `theme.dart` for highlightColor.
## 0.1.13
... ...
... ... @@ -81,7 +81,7 @@ Check the documentation [here.](https://github.com/saminsohag/flutter_packages/t
import 'package:flutter/material.dart';
import 'package:gpt_markdown/gpt_markdown.dart';
return TexMarkdown(
return GptMarkdown(
'''
* This is a unordered list.
''',
... ...
... ... @@ -5,9 +5,11 @@
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
... ...
... ... @@ -48,7 +48,7 @@ class _MyHomePageState extends State<MyHomePage> {
AnimatedBuilder(
animation: _controller,
builder: (context, _) {
return TexMarkdown(
return GptMarkdown(
_controller.text,
style: const TextStyle(
color: Colors.red,
... ... @@ -72,4 +72,4 @@ class _MyHomePageState extends State<MyHomePage> {
}
}
```
\ No newline at end of file
```
... ...
... ... @@ -30,16 +30,24 @@ class _MyAppState extends State<MyApp> {
useMaterial3: true,
brightness: Brightness.light,
colorSchemeSeed: Colors.blue,
extensions: [
GptMarkdownThemeData(
brightness: Brightness.light,
highlightColor: Colors.red,
),
],
),
darkTheme: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorSchemeSeed: Colors.blue,
extensions: [
GptMarkdownThemeData(
highlightColor: Colors.red,
),
]),
useMaterial3: true,
brightness: Brightness.dark,
colorSchemeSeed: Colors.blue,
extensions: [
GptMarkdownThemeData(
brightness: Brightness.dark,
highlightColor: Colors.red,
),
],
),
home: MyHomePage(
title: 'GptMarkdown',
onPressed: () {
... ... @@ -170,7 +178,10 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
Icons.select_all_outlined,
color: selectable
? Theme.of(context).colorScheme.onSurfaceVariant
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38),
: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.38),
),
),
IconButton(
... ... @@ -243,7 +254,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
// ),
child: Builder(
builder: (context) {
Widget child = TexMarkdown(
Widget child = GptMarkdown(
_controller.text,
textDirection: _direction,
onLinkTab: (url, title) {
... ... @@ -301,7 +312,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
..insert(1, "|---|");
tableString =
tableStringList.join("\n");
return TexMarkdown(tableString);
return GptMarkdown(tableString);
}
var controller = ScrollController();
Widget child = Math.tex(
... ...
... ... @@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
... ...
... ... @@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: args
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
url: "https://pub.dev"
source: hosted
version: "2.5.0"
version: "2.6.0"
async:
dependency: transitive
description:
... ... @@ -45,10 +45,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.19.0"
cross_file:
dependency: transitive
description:
... ... @@ -98,18 +98,18 @@ packages:
dependency: "direct main"
description:
name: flutter_math_fork
sha256: "94bee4642892a94939af0748c6a7de0ff8318feee588379dcdfea7dc5cba06c8"
sha256: "284bab89b2fbf1bc3a0baf13d011c1dd324d004e35d177626b77f2fc056366ac"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
version: "0.7.3"
flutter_svg:
dependency: transitive
description:
name: flutter_svg
sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123"
url: "https://pub.dev"
source: hosted
version: "2.0.10+1"
version: "2.0.16"
flutter_test:
dependency: "direct dev"
description: flutter
... ... @@ -126,7 +126,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.1.14"
version: "1.0.0"
http:
dependency: transitive
description:
... ... @@ -139,26 +139,26 @@ packages:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
version: "4.1.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
version: "10.0.7"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.0.8"
leak_tracker_testing:
dependency: transitive
description:
... ... @@ -219,10 +219,10 @@ packages:
dependency: transitive
description:
name: path_parsing
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
version: "1.1.0"
petitparser:
dependency: transitive
description:
... ... @@ -243,7 +243,7 @@ packages:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
version: "0.0.0"
source_span:
dependency: transitive
description:
... ... @@ -256,10 +256,10 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
version: "1.12.0"
stream_channel:
dependency: transitive
description:
... ... @@ -272,10 +272,10 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.3.0"
term_glyph:
dependency: transitive
description:
... ... @@ -288,10 +288,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
version: "0.7.3"
tuple:
dependency: transitive
description:
... ... @@ -304,34 +304,34 @@ packages:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version: "1.4.0"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7"
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
version: "1.1.15"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb"
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
version: "1.1.12"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
version: "1.1.16"
vector_math:
dependency: transitive
description:
... ... @@ -344,26 +344,26 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.3.0"
watcher:
dependency: "direct main"
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.1.1"
web:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "1.1.0"
xml:
dependency: transitive
description:
... ... @@ -374,4 +374,4 @@ packages:
version: "6.5.0"
sdks:
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
flutter: ">=3.22.0"
... ...
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class CodeField extends StatefulWidget {
const CodeField({super.key, required this.name, required this.codes});
final String name;
final String codes;
@override
State<CodeField> createState() => _CodeFieldState();
}
class _CodeFieldState extends State<CodeField> {
bool _copied = false;
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).colorScheme.onInverseSurface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(widget.name),
),
const Spacer(),
TextButton.icon(
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onSurface,
textStyle: const TextStyle(
fontWeight: FontWeight.normal,
),
),
onPressed: () async {
await Clipboard.setData(ClipboardData(text: widget.codes))
.then((value) {
setState(() {
_copied = true;
});
});
await Future.delayed(const Duration(seconds: 2));
setState(() {
_copied = false;
});
},
icon: Icon(
(_copied) ? Icons.done : Icons.content_paste,
size: 15,
),
label: Text((_copied) ? "Copied!" : "Copy code"),
),
],
),
const Divider(
height: 1,
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.all(16),
child: Text(
widget.codes,
),
),
],
),
);
}
}
... ...
... ... @@ -8,11 +8,15 @@ class LinkButton extends StatefulWidget {
final TextStyle? textStyle;
final String? url;
final GptMarkdownConfig config;
final Color color;
final Color hoverColor;
const LinkButton(
{super.key,
required this.text,
required this.config,
required this.color,
required this.hoverColor,
this.onPressed,
this.textStyle,
this.url});
... ... @@ -27,9 +31,9 @@ class _LinkButtonState extends State<LinkButton> {
@override
Widget build(BuildContext context) {
var style = (widget.config.style ?? const TextStyle()).copyWith(
color: _isHovering ? Colors.red : Colors.blue,
color: _isHovering ? widget.hoverColor : widget.color,
decoration: TextDecoration.underline,
decorationColor: _isHovering ? Colors.red : Colors.blue,
decorationColor: _isHovering ? widget.hoverColor : widget.color,
);
return MouseRegion(
cursor: SystemMouseCursors.click,
... ...
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class SelectableAdapter extends StatelessWidget {
const SelectableAdapter(
{super.key, required this.selectedText, required this.child});
final Widget child;
final String selectedText;
@override
Widget build(BuildContext context) {
final SelectionRegistrar? registrar = SelectionContainer.maybeOf(context);
if (registrar == null) {
return child;
}
return MouseRegion(
cursor: SystemMouseCursors.text,
child: _SelectableAdapter(
registrar: registrar,
selectedText: selectedText,
child: child,
),
);
}
}
class _SelectableAdapter extends SingleChildRenderObjectWidget {
const _SelectableAdapter({
required this.registrar,
required Widget child,
required this.selectedText,
}) : super(child: child);
final SelectionRegistrar registrar;
final String selectedText;
@override
_RenderSelectableAdapter createRenderObject(BuildContext context) {
return _RenderSelectableAdapter(
DefaultSelectionStyle.of(context).selectionColor!,
selectedText,
registrar,
);
}
@override
void updateRenderObject(
BuildContext context, _RenderSelectableAdapter renderObject) {
renderObject
..selectionColor = DefaultSelectionStyle.of(context).selectionColor!
..registrar = registrar;
}
}
class _RenderSelectableAdapter extends RenderProxyBox
with Selectable, SelectionRegistrant {
String selectionText;
_RenderSelectableAdapter(
Color selectionColor,
this.selectionText,
SelectionRegistrar registrar,
) : _selectionColor = selectionColor,
_geometry = ValueNotifier<SelectionGeometry>(_noSelection) {
this.registrar = registrar;
_geometry.addListener(markNeedsPaint);
}
static const SelectionGeometry _noSelection =
SelectionGeometry(status: SelectionStatus.none, hasContent: true);
final ValueNotifier<SelectionGeometry> _geometry;
Color get selectionColor => _selectionColor;
late Color _selectionColor;
set selectionColor(Color value) {
if (_selectionColor == value) {
return;
}
_selectionColor = value;
markNeedsPaint();
}
// ValueListenable APIs
@override
void addListener(VoidCallback listener) => _geometry.addListener(listener);
@override
void removeListener(VoidCallback listener) =>
_geometry.removeListener(listener);
@override
SelectionGeometry get value => _geometry.value;
// Selectable APIs.
@override
List<Rect> get boundingBoxes => <Rect>[paintBounds];
// Adjust this value to enlarge or shrink the selection highlight.
static const double _padding = 0.0;
Rect _getSelectionHighlightRect() {
return Rect.fromLTWH(0 - _padding, 0 - _padding, size.width + _padding * 2,
size.height + _padding * 2);
}
Offset? _start;
Offset? _end;
void _updateGeometry() {
if (_start == null || _end == null) {
_geometry.value = _noSelection;
return;
}
final Rect renderObjectRect = Rect.fromLTWH(0, 0, size.width, size.height);
final Rect selectionRect = Rect.fromPoints(_start!, _end!);
if (renderObjectRect.intersect(selectionRect).isEmpty) {
_geometry.value = _noSelection;
} else {
final Rect selectionRect = _getSelectionHighlightRect();
final SelectionPoint firstSelectionPoint = SelectionPoint(
localPosition: selectionRect.bottomLeft,
lineHeight: selectionRect.size.height,
handleType: TextSelectionHandleType.left,
);
final SelectionPoint secondSelectionPoint = SelectionPoint(
localPosition: selectionRect.bottomRight,
lineHeight: selectionRect.size.height,
handleType: TextSelectionHandleType.right,
);
final bool isReversed;
if (_start!.dy > _end!.dy) {
isReversed = true;
} else if (_start!.dy < _end!.dy) {
isReversed = false;
} else {
isReversed = _start!.dx > _end!.dx;
}
_geometry.value = SelectionGeometry(
status: SelectionStatus.uncollapsed,
hasContent: true,
startSelectionPoint:
isReversed ? secondSelectionPoint : firstSelectionPoint,
endSelectionPoint:
isReversed ? firstSelectionPoint : secondSelectionPoint,
selectionRects: <Rect>[selectionRect],
);
}
}
@override
SelectionResult dispatchSelectionEvent(SelectionEvent event) {
SelectionResult result = SelectionResult.none;
switch (event.type) {
case SelectionEventType.startEdgeUpdate:
case SelectionEventType.endEdgeUpdate:
final Rect renderObjectRect =
Rect.fromLTWH(0, 0, size.width, size.height);
// Normalize offset in case it is out side of the rect.
final Offset point =
globalToLocal((event as SelectionEdgeUpdateEvent).globalPosition);
final Offset adjustedPoint =
SelectionUtils.adjustDragOffset(renderObjectRect, point);
if (event.type == SelectionEventType.startEdgeUpdate) {
_start = adjustedPoint;
} else {
_end = adjustedPoint;
}
result = SelectionUtils.getResultBasedOnRect(renderObjectRect, point);
case SelectionEventType.clear:
_start = _end = null;
case SelectionEventType.selectAll:
case SelectionEventType.selectWord:
case SelectionEventType.selectParagraph:
_start = Offset.zero;
_end = Offset.infinite;
case SelectionEventType.granularlyExtendSelection:
result = SelectionResult.end;
final GranularlyExtendSelectionEvent extendSelectionEvent =
event as GranularlyExtendSelectionEvent;
// Initialize the offset it there is no ongoing selection.
if (_start == null || _end == null) {
if (extendSelectionEvent.forward) {
_start = _end = Offset.zero;
} else {
_start = _end = Offset.infinite;
}
}
// Move the corresponding selection edge.
final Offset newOffset =
extendSelectionEvent.forward ? Offset.infinite : Offset.zero;
if (extendSelectionEvent.isEnd) {
if (newOffset == _end) {
result = extendSelectionEvent.forward
? SelectionResult.next
: SelectionResult.previous;
}
_end = newOffset;
} else {
if (newOffset == _start) {
result = extendSelectionEvent.forward
? SelectionResult.next
: SelectionResult.previous;
}
_start = newOffset;
}
case SelectionEventType.directionallyExtendSelection:
result = SelectionResult.end;
final DirectionallyExtendSelectionEvent extendSelectionEvent =
event as DirectionallyExtendSelectionEvent;
// Convert to local coordinates.
final double horizontalBaseLine = globalToLocal(Offset(event.dx, 0)).dx;
final Offset newOffset;
final bool forward;
switch (extendSelectionEvent.direction) {
case SelectionExtendDirection.backward:
case SelectionExtendDirection.previousLine:
forward = false;
// Initialize the offset it there is no ongoing selection.
if (_start == null || _end == null) {
_start = _end = Offset.infinite;
}
// Move the corresponding selection edge.
if (extendSelectionEvent.direction ==
SelectionExtendDirection.previousLine ||
horizontalBaseLine < 0) {
newOffset = Offset.zero;
} else {
newOffset = Offset.infinite;
}
case SelectionExtendDirection.nextLine:
case SelectionExtendDirection.forward:
forward = true;
// Initialize the offset it there is no ongoing selection.
if (_start == null || _end == null) {
_start = _end = Offset.zero;
}
// Move the corresponding selection edge.
if (extendSelectionEvent.direction ==
SelectionExtendDirection.nextLine ||
horizontalBaseLine > size.width) {
newOffset = Offset.infinite;
} else {
newOffset = Offset.zero;
}
}
if (extendSelectionEvent.isEnd) {
if (newOffset == _end) {
result = forward ? SelectionResult.next : SelectionResult.previous;
}
_end = newOffset;
} else {
if (newOffset == _start) {
result = forward ? SelectionResult.next : SelectionResult.previous;
}
_start = newOffset;
}
}
_updateGeometry();
return result;
}
// This method is called when users want to copy selected content in this
// widget into clipboard.
@override
SelectedContent? getSelectedContent() {
return value.hasSelection
? SelectedContent(plainText: selectionText)
: null;
}
LayerLink? _startHandle;
LayerLink? _endHandle;
@override
void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle) {
if (_startHandle == startHandle && _endHandle == endHandle) {
return;
}
_startHandle = startHandle;
_endHandle = endHandle;
markNeedsPaint();
}
@override
void paint(PaintingContext context, Offset offset) {
super.paint(context, offset);
if (!_geometry.value.hasSelection) {
return;
}
// Draw the selection highlight.
final Paint selectionPaint = Paint()
..style = PaintingStyle.fill
..color = _selectionColor;
context.canvas
.drawRect(_getSelectionHighlightRect().shift(offset), selectionPaint);
// Push the layer links if any.
if (_startHandle != null) {
context.pushLayer(
LeaderLayer(
link: _startHandle!,
offset: offset + value.startSelectionPoint!.localPosition,
),
(PaintingContext context, Offset offset) {},
Offset.zero,
);
}
if (_endHandle != null) {
context.pushLayer(
LeaderLayer(
link: _endHandle!,
offset: offset + value.endSelectionPoint!.localPosition,
),
(PaintingContext context, Offset offset) {},
Offset.zero,
);
}
}
@override
void dispose() {
_geometry.dispose();
super.dispose();
}
}
... ...
... ... @@ -4,14 +4,15 @@ import 'package:flutter/material.dart';
import 'package:gpt_markdown/custom_widgets/markdow_config.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_math_fork/flutter_math.dart';
import 'package:gpt_markdown/custom_widgets/custom_divider.dart';
import 'package:gpt_markdown/custom_widgets/custom_error_image.dart';
import 'package:gpt_markdown/custom_widgets/custom_rb_cb.dart';
import 'package:gpt_markdown/custom_widgets/selectable_adapter.dart';
import 'package:gpt_markdown/custom_widgets/unordered_ordered_list.dart';
import 'dart:math';
import 'custom_widgets/code_field.dart';
import 'custom_widgets/link_button.dart';
part 'theme.dart';
... ... @@ -19,8 +20,8 @@ part 'markdown_component.dart';
part 'md_widget.dart';
/// This widget create a full markdown widget as a column view.
class TexMarkdown extends StatelessWidget {
const TexMarkdown(
class GptMarkdown extends StatelessWidget {
const GptMarkdown(
this.data, {
super.key,
this.style,
... ...
... ... @@ -2,24 +2,24 @@ part of 'gpt_markdown.dart';
/// Markdown components
abstract class MarkdownComponent {
static List<MarkdownComponent> components = [
static final List<MarkdownComponent> components = [
CodeBlockMd(),
NewLines(),
TableMd(),
HTag(),
IndentMd(),
UnOrderedList(),
OrderedList(),
RadioButtonMd(),
CheckBoxMd(),
HrLine(),
IndentMd(),
LatexMathMultyLine(),
LatexMath(),
ImageMd(),
HighlightedText(),
StrikeMd(),
BoldMd(),
ItalicMd(),
LatexMathMultyLine(),
LatexMath(),
ATagMd(),
SourceTag(),
];
... ... @@ -38,46 +38,42 @@ abstract class MarkdownComponent {
multiLine: true,
dotAll: true,
);
List<String> elements = [];
text.splitMapJoin(
combinedRegex,
onMatch: (p0) {
String element = p0[0] ?? "";
elements.add(element);
for (var each in components) {
if (each.exp.hasMatch(element)) {
if (each is InlineMd) {
if (each.inline) {
spans.add(each.span(
context,
element,
config,
));
} else {
if (each is BlockMd) {
spans.addAll([
TextSpan(
text: "\n ",
style: TextStyle(
fontSize: 0,
height: 0,
color: config.style?.color,
),
spans.addAll([
TextSpan(
text: "\n ",
style: TextStyle(
fontSize: 0,
height: 0,
color: config.style?.color,
),
each.span(
context,
element,
config,
),
TextSpan(
text: "\n ",
style: TextStyle(
fontSize: 0,
height: 0,
color: config.style?.color,
),
),
each.span(
context,
element,
config,
),
TextSpan(
text: "\n ",
style: TextStyle(
fontSize: 0,
height: 0,
color: config.style?.color,
),
]);
}
),
]);
}
return "";
}
... ... @@ -125,18 +121,38 @@ abstract class InlineMd extends MarkdownComponent {
abstract class BlockMd extends MarkdownComponent {
@override
bool get inline => false;
@override
RegExp get exp => RegExp(
r'^\ *?' + expString,
dotAll: true,
multiLine: true,
);
String get expString;
@override
InlineSpan span(
BuildContext context,
String text,
final GptMarkdownConfig config,
) {
var matches = RegExp(r'^(?<spaces>\ \ +).*').firstMatch(text);
var spaces = matches?.namedGroup('spaces');
var length = spaces?.length ?? 0;
var child = build(
context,
text,
config,
);
if (length > 0) {
child = UnorderedListView(
spacing: length.toDouble() * 6,
child: child,
);
}
return WidgetSpan(
child: build(
context,
text,
config,
),
child: child,
alignment: PlaceholderAlignment.middle,
);
}
... ... @@ -151,57 +167,44 @@ abstract class BlockMd extends MarkdownComponent {
/// Heading component
class HTag extends BlockMd {
@override
RegExp get exp => RegExp(r"^(#{1,6})\ ([^\n]+?)$");
String get expString => (r"(?<hash>#{1,6})\ (?<data>[^\n]+?)$");
@override
Widget build(
BuildContext context,
String text,
final GptMarkdownConfig config,
) {
var match = exp.firstMatch(text.trim());
var theme = GptMarkdownTheme.of(context);
var match = this.exp.firstMatch(text.trim());
var conf = config.copyWith(
style: [
Theme.of(context)
.textTheme
.headlineLarge
?.copyWith(color: config.style?.color),
Theme.of(context)
.textTheme
.headlineMedium
?.copyWith(color: config.style?.color),
Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(color: config.style?.color),
Theme.of(context)
.textTheme
.titleLarge
?.copyWith(color: config.style?.color),
Theme.of(context)
.textTheme
.titleMedium
?.copyWith(color: config.style?.color),
Theme.of(context)
.textTheme
.titleSmall
?.copyWith(color: config.style?.color),
][match![1]!.length - 1]);
style: [
theme.h1,
theme.h2,
theme.h3,
theme.h4,
theme.h5,
theme.h6,
][match![1]!.length - 1]
?.copyWith(
color: config.style?.color,
),
);
return config.getRich(
TextSpan(
children: [
...(MarkdownComponent.generate(
context,
"${match[2]}",
"${match.namedGroup('data')}",
conf,
)),
if (match[1]!.length == 1) ...[
if (match.namedGroup('hash')!.length == 1) ...[
const TextSpan(
text: "\n ",
style: TextStyle(fontSize: 0, height: 0),
),
WidgetSpan(
child: CustomDivider(
height: 2,
height: theme.hrLineThickness,
color: config.style?.color ??
Theme.of(context).colorScheme.outline,
),
... ... @@ -235,16 +238,18 @@ class NewLines extends InlineMd {
/// Horizontal line component
class HrLine extends BlockMd {
@override
RegExp get exp => RegExp(r"^(--)[-]+$");
String get expString => (r"(--)[-]+$");
@override
Widget build(
BuildContext context,
String text,
final GptMarkdownConfig config,
) {
var thickness = GptMarkdownTheme.of(context).hrLineThickness;
var color = GptMarkdownTheme.of(context).hrLineColor;
return CustomDivider(
height: 2,
color: config.style?.color ?? Theme.of(context).colorScheme.outline,
height: thickness,
color: config.style?.color ?? color,
);
}
}
... ... @@ -252,7 +257,7 @@ class HrLine extends BlockMd {
/// Checkbox component
class CheckBoxMd extends BlockMd {
@override
RegExp get exp => RegExp(r"^\[(\x?)\]\ (\S[^\n]*?)$");
String get expString => (r"\[(\x?)\]\ (\S[^\n]*?)$");
get onLinkTab => null;
@override
... ... @@ -261,7 +266,7 @@ class CheckBoxMd extends BlockMd {
String text,
final GptMarkdownConfig config,
) {
var match = exp.firstMatch(text.trim());
var match = this.exp.firstMatch(text.trim());
return CustomCb(
value: ("${match?[1]}" == "x"),
textDirection: config.textDirection,
... ... @@ -276,7 +281,7 @@ class CheckBoxMd extends BlockMd {
/// Radio Button component
class RadioButtonMd extends BlockMd {
@override
RegExp get exp => RegExp(r"^\((\x?)\)\ (\S[^\n]*)$");
String get expString => (r"\((\x?)\)\ (\S[^\n]*)$");
get onLinkTab => null;
@override
... ... @@ -285,7 +290,7 @@ class RadioButtonMd extends BlockMd {
String text,
final GptMarkdownConfig config,
) {
var match = exp.firstMatch(text.trim());
var match = this.exp.firstMatch(text.trim());
return CustomRb(
value: ("${match?[1]}" == "x"),
textDirection: config.textDirection,
... ... @@ -298,36 +303,45 @@ class RadioButtonMd extends BlockMd {
}
/// Indent
class IndentMd extends BlockMd {
class IndentMd extends InlineMd {
@override
RegExp get exp => RegExp(r"^(\ \ \ \ +)([^\n]+)$");
get onLinkTab => null;
RegExp get exp =>
RegExp(r"^(\ +)(((?!\n\n).)+)$", dotAll: true, multiLine: true);
@override
Widget build(
InlineSpan span(
BuildContext context,
String text,
final GptMarkdownConfig config,
) {
[
r"\\\[(.*?)\\\]",
r"\\\((.*?)\\\)",
r"(?<!\\)\$((?:\\.|[^$])*?)\$(?!\\)",
].join("|");
var match = exp.firstMatch(text);
int spaces = (match?[1] ?? "").length;
return UnorderedListView(
bulletColor: config.style?.color,
padding: spaces * 5,
bulletSize: 0,
textDirection: config.textDirection,
child: Text.rich(TextSpan(
children: MarkdownComponent.generate(
context,
"${match?[2]}",
config,
var data = "${match?[2]}".trim();
data.replaceAll(RegExp(r'\n\ *'), '\n').trim();
var child = TextSpan(
children: MarkdownComponent.generate(
context,
data,
config,
),
);
if (spaces < 4) {
return child;
}
return TextSpan(
children: [
const TextSpan(text: '\n'),
WidgetSpan(
child: UnorderedListView(
bulletColor: config.style?.color,
padding: spaces * 5,
bulletSize: 0,
textDirection: config.textDirection,
child: Text.rich(child),
),
),
)),
const TextSpan(text: '\n'),
],
);
}
}
... ... @@ -335,7 +349,7 @@ class IndentMd extends BlockMd {
/// Unordered list component
class UnOrderedList extends BlockMd {
@override
RegExp get exp => RegExp(r"^(?:\-|\*)\ ([^\n]+)$");
String get expString => (r"(?:\-|\*)\ ([^\n]+)$");
get onLinkTab => null;
@override
... ... @@ -344,7 +358,7 @@ class UnOrderedList extends BlockMd {
String text,
final GptMarkdownConfig config,
) {
var match = exp.firstMatch(text);
var match = this.exp.firstMatch(text);
return UnorderedListView(
bulletColor:
config.style?.color ?? DefaultTextStyle.of(context).style.color,
... ... @@ -365,7 +379,7 @@ class UnOrderedList extends BlockMd {
/// Ordered list component
class OrderedList extends BlockMd {
@override
RegExp get exp => RegExp(r"^([0-9]+\.)\ ([^\n]+)$");
String get expString => (r"([0-9]+\.)\ ([^\n]+)$");
get onLinkTab => null;
... ... @@ -375,7 +389,7 @@ class OrderedList extends BlockMd {
String text,
final GptMarkdownConfig config,
) {
var match = exp.firstMatch(text.trim());
var match = this.exp.firstMatch(text.trim());
return OrderedListView(
no: "${match?[1]}",
textDirection: config.textDirection,
... ... @@ -508,10 +522,9 @@ class ItalicMd extends InlineMd {
class LatexMathMultyLine extends BlockMd {
@override
RegExp get exp => RegExp(
r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})",
dotAll: true,
);
String get expString => (r"\\\[(((?!\n\n).)*)\\\]|(\\begin.*?\\end{.*?})");
@override
RegExp get exp => RegExp(expString, dotAll: true, multiLine: true);
@override
Widget build(
... ... @@ -520,48 +533,50 @@ class LatexMathMultyLine extends BlockMd {
final GptMarkdownConfig config,
) {
var p0 = exp.firstMatch(text.trim());
p0?.group(0);
String mathText = p0?[1] ?? p0?[2] ?? "";
var workaround = config.latexWorkaround ?? (String tex) => tex;
var builder = config.latexBuilder ??
(BuildContext context, String tex, TextStyle textStyle, bool inline) =>
Math.tex(
tex,
textStyle: textStyle,
mathStyle: MathStyle.display,
textScaleFactor: 1,
settings: const TexParserSettings(
strict: Strict.ignore,
),
options: MathOptions(
sizeUnderTextStyle: MathSize.large,
color: config.style?.color ??
Theme.of(context).colorScheme.onSurface,
fontSize: config.style?.fontSize ??
Theme.of(context).textTheme.bodyMedium?.fontSize,
mathFontOptions: FontOptions(
fontFamily: "Main",
fontWeight: config.style?.fontWeight ?? FontWeight.normal,
fontShape: FontStyle.normal,
SelectableAdapter(
selectedText: tex,
child: Math.tex(
tex,
textStyle: textStyle,
mathStyle: MathStyle.display,
textScaleFactor: 1,
settings: const TexParserSettings(
strict: Strict.ignore,
),
textFontOptions: FontOptions(
fontFamily: "Main",
fontWeight: config.style?.fontWeight ?? FontWeight.normal,
fontShape: FontStyle.normal,
options: MathOptions(
sizeUnderTextStyle: MathSize.large,
color: config.style?.color ??
Theme.of(context).colorScheme.onSurface,
fontSize: config.style?.fontSize ??
Theme.of(context).textTheme.bodyMedium?.fontSize,
mathFontOptions: FontOptions(
fontFamily: "Main",
fontWeight: config.style?.fontWeight ?? FontWeight.normal,
fontShape: FontStyle.normal,
),
textFontOptions: FontOptions(
fontFamily: "Main",
fontWeight: config.style?.fontWeight ?? FontWeight.normal,
fontShape: FontStyle.normal,
),
style: MathStyle.display,
),
style: MathStyle.display,
onErrorFallback: (err) {
return Text(
workaround(mathText),
textDirection: config.textDirection,
style: textStyle.copyWith(
color: (!kDebugMode)
? null
: Theme.of(context).colorScheme.error),
);
},
),
onErrorFallback: (err) {
return Text(
workaround(mathText),
textDirection: config.textDirection,
style: textStyle.copyWith(
color: (!kDebugMode)
? null
: Theme.of(context).colorScheme.error),
);
},
);
return builder(context, workaround(mathText),
config.style ?? const TextStyle(), false);
... ... @@ -591,42 +606,45 @@ class LatexMath extends InlineMd {
var workaround = config.latexWorkaround ?? (String tex) => tex;
var builder = config.latexBuilder ??
(BuildContext context, String tex, TextStyle textStyle, bool inline) =>
Math.tex(
tex,
textStyle: textStyle,
mathStyle: MathStyle.display,
textScaleFactor: 1,
settings: const TexParserSettings(
strict: Strict.ignore,
),
options: MathOptions(
sizeUnderTextStyle: MathSize.large,
color: config.style?.color ??
Theme.of(context).colorScheme.onSurface,
fontSize: config.style?.fontSize ??
Theme.of(context).textTheme.bodyMedium?.fontSize,
mathFontOptions: FontOptions(
fontFamily: "Main",
fontWeight: config.style?.fontWeight ?? FontWeight.normal,
fontShape: FontStyle.normal,
SelectableAdapter(
selectedText: tex,
child: Math.tex(
tex,
textStyle: textStyle,
mathStyle: MathStyle.display,
textScaleFactor: 1,
settings: const TexParserSettings(
strict: Strict.ignore,
),
textFontOptions: FontOptions(
fontFamily: "Main",
fontWeight: config.style?.fontWeight ?? FontWeight.normal,
fontShape: FontStyle.normal,
options: MathOptions(
sizeUnderTextStyle: MathSize.large,
color: config.style?.color ??
Theme.of(context).colorScheme.onSurface,
fontSize: config.style?.fontSize ??
Theme.of(context).textTheme.bodyMedium?.fontSize,
mathFontOptions: FontOptions(
fontFamily: "Main",
fontWeight: config.style?.fontWeight ?? FontWeight.normal,
fontShape: FontStyle.normal,
),
textFontOptions: FontOptions(
fontFamily: "Main",
fontWeight: config.style?.fontWeight ?? FontWeight.normal,
fontShape: FontStyle.normal,
),
style: MathStyle.display,
),
style: MathStyle.display,
onErrorFallback: (err) {
return Text(
workaround(mathText),
textDirection: config.textDirection,
style: textStyle.copyWith(
color: (!kDebugMode)
? null
: Theme.of(context).colorScheme.error),
);
},
),
onErrorFallback: (err) {
return Text(
workaround(mathText),
textDirection: config.textDirection,
style: textStyle.copyWith(
color: (!kDebugMode)
? null
: Theme.of(context).colorScheme.error),
);
},
);
return WidgetSpan(
alignment: PlaceholderAlignment.baseline,
... ... @@ -655,7 +673,6 @@ class SourceTag extends InlineMd {
}
return WidgetSpan(
alignment: PlaceholderAlignment.middle,
// baseline: TextBaseline.alphabetic,
child: Padding(
padding: const EdgeInsets.all(2),
child: config.sourceTagBuilder
... ... @@ -696,8 +713,11 @@ class ATagMd extends InlineMd {
if (match?[1] == null && match?[2] == null) {
return const TextSpan();
}
var theme = GptMarkdownTheme.of(context);
return WidgetSpan(
child: LinkButton(
hoverColor: theme.linkHoverColor,
color: theme.linkColor,
onPressed: () {
config.onLinkTab?.call("${match?[2]}", "${match?[1]}");
},
... ... @@ -762,6 +782,9 @@ class ImageMd extends InlineMd {
/// Table component
class TableMd extends BlockMd {
@override
String get expString =>
(r"(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)$");
@override
Widget build(
BuildContext context,
String text,
... ... @@ -847,28 +870,19 @@ class TableMd extends BlockMd {
),
);
}
@override
RegExp get exp => RegExp(
r"^(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)$",
);
}
class CodeBlockMd extends BlockMd {
@override
RegExp get exp => RegExp(
r"\s*?```(.*?)\n((.*?)(:?\n\s*?```)|(.*)(:?\n```)?)$",
multiLine: true,
dotAll: true,
);
String get expString => r"```(.*?)\n((.*?)(:?\n\s*?```)|(.*)(:?\n```)?)$";
@override
Widget build(
BuildContext context,
String text,
final GptMarkdownConfig config,
) {
String codes = exp.firstMatch(text)?[2] ?? "";
String name = exp.firstMatch(text)?[1] ?? "";
String codes = this.exp.firstMatch(text)?[2] ?? "";
String name = this.exp.firstMatch(text)?[1] ?? "";
codes = codes.replaceAll(r"```", "").trim();
return Padding(
padding: const EdgeInsets.all(16.0),
... ... @@ -878,75 +892,3 @@ class CodeBlockMd extends BlockMd {
);
}
}
class CodeField extends StatefulWidget {
const CodeField({super.key, required this.name, required this.codes});
final String name;
final String codes;
@override
State<CodeField> createState() => _CodeFieldState();
}
class _CodeFieldState extends State<CodeField> {
bool _copied = false;
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).colorScheme.onInverseSurface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(widget.name),
),
const Spacer(),
TextButton.icon(
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onSurface,
textStyle: const TextStyle(
fontWeight: FontWeight.normal,
),
),
onPressed: () async {
await Clipboard.setData(ClipboardData(text: widget.codes))
.then((value) {
setState(() {
_copied = true;
});
});
await Future.delayed(const Duration(seconds: 2));
setState(() {
_copied = false;
});
},
icon: Icon(
(_copied) ? Icons.done : Icons.content_paste,
size: 15,
),
label: Text((_copied) ? "Copied!" : "Copy code"),
),
],
),
const Divider(
height: 1,
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.all(16),
child: Text(
widget.codes,
),
),
],
),
);
}
}
... ...
... ... @@ -16,16 +16,17 @@ class MdWidget extends StatelessWidget {
list.addAll(
MarkdownComponent.generate(
context,
exp.replaceAllMapped(
RegExp(
r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})",
multiLine: true,
dotAll: true,
), (match) {
//
String body = (match[1] ?? match[2])?.replaceAll("\n", " ") ?? "";
return "\\[$body\\]";
}),
exp,
// .replaceAllMapped(
// RegExp(
// r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})",
// multiLine: true,
// dotAll: true,
// ), (match) {
// //
// String body = (match[1] ?? match[2])?.replaceAll("\n", " ") ?? "";
// return "\\[$body\\]";
// }),
config,
),
);
... ...
part of 'gpt_markdown.dart';
/// Theme defined for `TexMarkdown` widget
/// Theme defined for `GptMarkdown` widget
class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> {
GptMarkdownThemeData({
GptMarkdownThemeData._({
required this.highlightColor,
required this.h1,
required this.h2,
required this.h3,
required this.h4,
required this.h5,
required this.h6,
required this.hrLineThickness,
required this.hrLineColor,
required this.linkColor,
required this.linkHoverColor,
});
/// Define default attributes.
factory GptMarkdownThemeData.from(BuildContext context) {
return GptMarkdownThemeData(
highlightColor:
Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(50),
factory GptMarkdownThemeData({
required Brightness brightness,
Color? highlightColor,
TextStyle? h1,
TextStyle? h2,
TextStyle? h3,
TextStyle? h4,
TextStyle? h5,
TextStyle? h6,
double? hrLineThickness,
Color? hrLineColor,
Color? linkColor,
Color? linkHoverColor,
}) {
ThemeData themeData = switch (brightness) {
Brightness.light => ThemeData.light(),
Brightness.dark => ThemeData.dark(),
};
final typography = Typography.tall2021.copyWith(
displayLarge: Typography.tall2021.displayLarge?.copyWith(inherit: true),
displayMedium: Typography.tall2021.displayMedium?.copyWith(inherit: true),
displaySmall: Typography.tall2021.displaySmall?.copyWith(inherit: true),
headlineLarge: Typography.tall2021.headlineLarge?.copyWith(inherit: true),
headlineMedium:
Typography.tall2021.headlineMedium?.copyWith(inherit: true),
headlineSmall: Typography.tall2021.headlineSmall?.copyWith(inherit: true),
titleLarge: Typography.tall2021.titleLarge?.copyWith(inherit: true),
titleMedium: Typography.tall2021.titleMedium?.copyWith(inherit: true),
titleSmall: Typography.tall2021.titleSmall?.copyWith(inherit: true),
bodyLarge: Typography.tall2021.bodyLarge?.copyWith(inherit: true),
bodyMedium: Typography.tall2021.bodyMedium?.copyWith(inherit: true),
bodySmall: Typography.tall2021.bodySmall?.copyWith(inherit: true),
labelLarge: Typography.tall2021.labelLarge?.copyWith(inherit: true),
labelMedium: Typography.tall2021.labelMedium?.copyWith(inherit: true),
labelSmall: Typography.tall2021.labelSmall?.copyWith(inherit: true),
);
themeData = themeData.copyWith(
textTheme: typography,
);
TextTheme textTheme = themeData.textTheme;
return GptMarkdownThemeData._fromTheme(themeData, textTheme).copyWith(
highlightColor: highlightColor,
h1: h1,
h2: h2,
h3: h3,
h4: h4,
h5: h5,
h6: h6,
hrLineThickness: hrLineThickness,
hrLineColor: hrLineColor,
linkColor: linkColor,
linkHoverColor: linkHoverColor,
);
}
factory GptMarkdownThemeData._fromTheme(
ThemeData theme, TextTheme textTheme) {
return GptMarkdownThemeData._(
highlightColor: theme.colorScheme.onSurfaceVariant.withAlpha(50),
h1: textTheme.headlineLarge,
h2: textTheme.headlineMedium,
h3: textTheme.headlineSmall,
h4: textTheme.titleLarge,
h5: textTheme.titleMedium,
h6: textTheme.titleSmall,
hrLineThickness: 1,
hrLineColor: theme.colorScheme.outline,
linkColor: Colors.blue,
linkHoverColor: Colors.red,
);
}
Color highlightColor;
TextStyle? h1;
TextStyle? h2;
TextStyle? h3;
TextStyle? h4;
TextStyle? h5;
TextStyle? h6;
double hrLineThickness;
Color hrLineColor;
Color linkColor;
Color linkHoverColor;
/// Define default attributes.
@override
GptMarkdownThemeData copyWith({Color? highlightColor}) {
return GptMarkdownThemeData(
GptMarkdownThemeData copyWith({
Color? highlightColor,
TextStyle? h1,
TextStyle? h2,
TextStyle? h3,
TextStyle? h4,
TextStyle? h5,
TextStyle? h6,
double? hrLineThickness,
Color? hrLineColor,
Color? linkColor,
Color? linkHoverColor,
}) {
return GptMarkdownThemeData._(
highlightColor: highlightColor ?? this.highlightColor,
h1: h1 ?? this.h1,
h2: h2 ?? this.h2,
h3: h3 ?? this.h3,
h4: h4 ?? this.h4,
h5: h5 ?? this.h5,
h6: h6 ?? this.h6,
hrLineThickness: hrLineThickness ?? this.hrLineThickness,
hrLineColor: hrLineColor ?? this.hrLineColor,
linkColor: linkColor ?? this.linkColor,
linkHoverColor: linkHoverColor ?? this.linkHoverColor,
);
}
... ... @@ -28,9 +135,21 @@ class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> {
if (other == null) {
return this;
}
return GptMarkdownThemeData(
return GptMarkdownThemeData._(
highlightColor:
Color.lerp(highlightColor, other.highlightColor, t) ?? highlightColor,
h1: TextStyle.lerp(h1, other.h1, t) ?? h1,
h2: TextStyle.lerp(h2, other.h2, t) ?? h2,
h3: TextStyle.lerp(h3, other.h3, t) ?? h3,
h4: TextStyle.lerp(h4, other.h4, t) ?? h4,
h5: TextStyle.lerp(h5, other.h5, t) ?? h5,
h6: TextStyle.lerp(h6, other.h6, t) ?? h6,
hrLineThickness: Tween(begin: hrLineThickness, end: other.hrLineThickness)
.transform(t),
hrLineColor: Color.lerp(hrLineColor, other.hrLineColor, t) ?? hrLineColor,
linkColor: Color.lerp(linkColor, other.linkColor, t) ?? linkColor,
linkHoverColor:
Color.lerp(linkHoverColor, other.linkHoverColor, t) ?? linkHoverColor,
);
}
}
... ... @@ -45,16 +164,17 @@ class GptMarkdownTheme extends InheritedWidget {
final GptMarkdownThemeData gptThemeData;
static GptMarkdownThemeData of(BuildContext context) {
var theme = Theme.of(context);
final provider =
context.dependOnInheritedWidgetOfExactType<GptMarkdownTheme>();
if (provider != null) {
return provider.gptThemeData;
}
final themeData = Theme.of(context).extension<GptMarkdownThemeData>();
final themeData = theme.extension<GptMarkdownThemeData>();
if (themeData != null) {
return themeData;
}
return GptMarkdownThemeData.from(context);
return GptMarkdownThemeData._fromTheme(theme, theme.textTheme);
}
@override
... ...
name: gpt_markdown
description: "The purpose of this package is to render the response of ChatGPT into a Flutter app."
version: 0.1.14
description: "Powerful Markdown & LaTeX Renderer for Flutter: Rich Text, Math, Tables, Links, and Text Selection. Ideal for ChatGPT, Gemini, and more."
version: 1.0.0
homepage: https://github.com/Infinitix-LLC/gpt_markdown
environment:
... ... @@ -10,46 +10,21 @@ environment:
dependencies:
flutter:
sdk: flutter
flutter_math_fork: ^0.7.2
flutter_math_fork: ^0.7.3
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^4.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages
topics:
- markdown
- latex
- selectable
- math
- chatgpt
- gemini
- ai
- gpt
... ...